diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e55fbbab62..ed5438fbfa 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -2,85 +2,85 @@ name: Bug Report description: Report an Insomnia bug labels: [B-bug, S-unverified] body: -- type: textarea - attributes: - label: Expected Behavior - description: A clear and concise description of what you expected to happen. - validations: - required: true -- type: markdown - attributes: - value: '> **Tip**: You can attach images or log files to textareas by clicking to highlight and then dragging files in.' -- type: textarea - attributes: - label: Actual Behavior - description: A clear description of what actually happens. - validations: - required: true -- type: textarea - attributes: - label: Reproduction Steps - description: Provide steps to reproduce the behavior - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error -- type: checkboxes - attributes: - label: Is there an existing issue for this? - options: - - label: I have searched the [issue tracker](https://www.github.com/Kong/insomnia/issues) for this problem. - required: true -- type: checkboxes - attributes: - label: Which sync method do you use? - options: - - label: Git sync. - - label: Insomnia Cloud sync. - - label: Local only -- type: textarea - attributes: - label: Additional Information - description: If your problem needs further explanation, please add more information here. -- type: input - attributes: - label: Insomnia Version - description: What version of Insmonia are you using? - placeholder: "2021.5.0" - validations: - required: true -- type: dropdown - attributes: - label: What operating system are you using? - options: - - Windows - - macOS - - Ubuntu - - Other Linux - - Other (specify below) - validations: - required: true -- type: input - attributes: - label: Operating System Version - description: |- - What operating system version are you using? - On Windows, click Start button > Settings > System > About. - On macOS, click the Apple Menu > About This Mac. - On Linux, use `lsb_release` or `uname -a`. - placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04" - validations: - required: true -- type: input - attributes: - label: Installation method - description: How did you install Insomnia? - placeholder: "e.g. download from insomnia.rest, homebrew, apt, etc." - validations: - required: true -- type: input - attributes: - label: Last Known Working Insomnia version - description: What is the last version of Insomnia this worked in, if applicable? - placeholder: "2021.4.0" + - type: textarea + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + - type: markdown + attributes: + value: '> **Tip**: You can attach images or log files to textareas by clicking to highlight and then dragging files in.' + - type: textarea + attributes: + label: Actual Behavior + description: A clear description of what actually happens. + validations: + required: true + - type: textarea + attributes: + label: Reproduction Steps + description: Provide steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + - type: checkboxes + attributes: + label: Is there an existing issue for this? + options: + - label: I have searched the [issue tracker](https://www.github.com/Kong/insomnia/issues) for this problem. + required: true + - type: checkboxes + attributes: + label: Which sync method do you use? + options: + - label: Git sync. + - label: Insomnia Cloud sync. + - label: Local only + - type: textarea + attributes: + label: Additional Information + description: If your problem needs further explanation, please add more information here. + - type: input + attributes: + label: Insomnia Version + description: What version of Insmonia are you using? + placeholder: '2021.5.0' + validations: + required: true + - type: dropdown + attributes: + label: What operating system are you using? + options: + - Windows + - macOS + - Ubuntu + - Other Linux + - Other (specify below) + validations: + required: true + - type: input + attributes: + label: Operating System Version + description: |- + What operating system version are you using? + On Windows, click Start button > Settings > System > About. + On macOS, click the Apple Menu > About This Mac. + On Linux, use `lsb_release` or `uname -a`. + placeholder: 'e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04' + validations: + required: true + - type: input + attributes: + label: Installation method + description: How did you install Insomnia? + placeholder: 'e.g. download from insomnia.rest, homebrew, apt, etc.' + validations: + required: true + - type: input + attributes: + label: Last Known Working Insomnia version + description: What is the last version of Insomnia this worked in, if applicable? + placeholder: '2021.4.0' diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index c22fcae227..a273c7b224 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -74,7 +74,7 @@ jobs: - name: Package app (Linux ARM64 only) if: runner.os == 'Linux' && runner.arch == 'ARM64' shell: bash - run: npm run app-package + run: npm run app-package env: NODE_OPTIONS: '--max_old_space_size=6144' BUILD_TARGETS: AppImage,tar.gz @@ -112,7 +112,6 @@ jobs: shell: bash run: ./build-secure-wrapper.sh CI - - name: Move .dll and .exe files to /tosign (PowerShell) if: runner.os == 'Windows' shell: pwsh @@ -158,7 +157,6 @@ jobs: CREDENTIAL_ID: ${{secrets.ES_CREDENTIAL_ID}} TOTP_SECRET: ${{secrets.ES_TOTP_SECRET}} - - name: Package inso run: | echo "Replacing electron binary with node binary" @@ -182,7 +180,7 @@ jobs: VERSION: ${{ env.INSO_VERSION }} - name: Notarize Inso CLI installer (macOS only) - if: runner.os == 'macOS' + if: runner.os == 'macOS' uses: lando/notarize-action@b5c3ef16cf2fbcf2af26dc58c90255ec242abeed # v2 with: product-path: ./packages/${{ env.INSO_PACKAGE_NAME }}/artifacts/inso-${{ matrix.os }}-${{ env.INSO_VERSION }}.pkg @@ -273,7 +271,7 @@ jobs: id: release_version shell: bash run: | - echo "version=${BRANCH/release\//}" >> $GITHUB_OUTPUT + echo "version=${BRANCH/release\//}" >> $GITHUB_OUTPUT env: BRANCH: ${{ github.ref_name }} diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 6d4a32e9a5..797a4f3d73 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -43,7 +43,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version-file: ".nvmrc" + node-version-file: '.nvmrc' cache: 'npm' cache-dependency-path: package-lock.json @@ -69,7 +69,7 @@ jobs: type=raw,value=latest,enable=${{ env.IS_PRERELEASE == 'false' }} type=raw,value=alpha,enable=${{ env.IS_PRERELEASE == 'true' && contains(github.event.inputs.version, 'alpha') }} type=raw,value=beta,enable=${{ env.IS_PRERELEASE == 'true' && contains(github.event.inputs.version, 'beta') }} - sep-tags: "," + sep-tags: ',' # Setup regctl to parse platform specific image digest from image manifest - name: Install regctl @@ -100,11 +100,11 @@ jobs: - name: Verify Inso Container Image Provenance produced on insomnia-ee run: | - slsa-verifier verify-image \ - kong/inso:${{env.RELEASE_VERSION}}@${{steps.image_manifest_metadata.outputs.inso_image_sha}} \ - --print-provenance \ - --provenance-repository ${{env.NOTARY_REPOSITORY}} \ - --source-uri 'github.com/Kong/insomnia-ee' + slsa-verifier verify-image \ + kong/inso:${{env.RELEASE_VERSION}}@${{steps.image_manifest_metadata.outputs.inso_image_sha}} \ + --print-provenance \ + --provenance-repository ${{env.NOTARY_REPOSITORY}} \ + --source-uri 'github.com/Kong/insomnia-ee' - name: Verify Inso Binary Provenance for artifacts produced on insomnia-ee run: | @@ -127,7 +127,7 @@ jobs: id: core_tag_and_release with: tag: ${{ env.RELEASE_CORE_TAG }} - name: "${{ env.RELEASE_VERSION }} 📦" + name: '${{ env.RELEASE_VERSION }} 📦' generateReleaseNotes: true commit: ${{ env.RELEASE_BRANCH }} prerelease: ${{ env.IS_PRERELEASE }} @@ -142,7 +142,7 @@ jobs: with: release_id: ${{ steps.core_tag_and_release.outputs.id }} tag_name: ${{ env.RELEASE_CORE_TAG }} - file: "./artifacts/*" + file: './artifacts/*' prerelease: ${{ env.IS_PRERELEASE }} draft: false diff --git a/.github/workflows/release-recurring.yml b/.github/workflows/release-recurring.yml index b3fd618bd9..e9f2e513d9 100644 --- a/.github/workflows/release-recurring.yml +++ b/.github/workflows/release-recurring.yml @@ -41,7 +41,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version-file: ".nvmrc" + node-version-file: '.nvmrc' cache: 'npm' cache-dependency-path: package-lock.json diff --git a/.github/workflows/release-start.yml b/.github/workflows/release-start.yml index f1b52f9603..04f9afdf90 100644 --- a/.github/workflows/release-start.yml +++ b/.github/workflows/release-start.yml @@ -9,9 +9,9 @@ on: type: choice description: Channel of the release (alpha/beta/stable) options: - - alpha - - beta - - stable + - alpha + - beta + - stable version: required: false description: force version of the release (e.g. 9.0.0) if previous release was successful, this should auto increment @@ -31,7 +31,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version-file: ".nvmrc" + node-version-file: '.nvmrc' cache: 'npm' cache-dependency-path: package-lock.json @@ -57,8 +57,7 @@ jobs: if: github.event.inputs.channel != 'stable' && github.event.inputs.version run: npm --workspaces version "${{ github.event.inputs.version }}" - - # ############################################################ + # ############################################################ - name: Get version shell: bash @@ -85,8 +84,8 @@ jobs: with: username: ${{ (github.event_name == 'workflow_dispatch' && github.actor) || 'insomnia-infra' }} - # ############################################################ - # re-run the versioning steps to apply to the new branch + # ############################################################ + # re-run the versioning steps to apply to the new branch - name: (Re-run) App version (stable, patch latest stable) if: github.event.inputs.channel == 'stable' && !github.event.inputs.version @@ -104,7 +103,7 @@ jobs: if: github.event.inputs.channel != 'stable' && github.event.inputs.version run: npm --workspaces version "${{ github.event.inputs.version }}" - # ############################################################ + # ############################################################ - name: Git Commit run: git commit -am "Bump app version to ${{ env.RELEASE_VERSION }}" @@ -122,7 +121,7 @@ jobs: github_token: ${{ secrets.RELEASE_GH_TOKEN }} source_branch: ${{ env.RELEASE_BRANCH}} target_branch: develop - title: ":rocket: ${{ env.RELEASE_VERSION}}" + title: ':rocket: ${{ env.RELEASE_VERSION}}' body: | **Automated pull request** Artifacts build in progress... diff --git a/.github/workflows/sast.yml b/.github/workflows/sast.yml index dcbd430429..adc687acd0 100644 --- a/.github/workflows/sast.yml +++ b/.github/workflows/sast.yml @@ -4,11 +4,10 @@ on: pull_request: {} push: branches: - - develop - - release/* + - develop + - release/* workflow_dispatch: {} - jobs: semgrep: timeout-minutes: 5 diff --git a/.github/workflows/test-cli.yml b/.github/workflows/test-cli.yml index 7742857d6b..7ae89ce2fa 100644 --- a/.github/workflows/test-cli.yml +++ b/.github/workflows/test-cli.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version-file: ".nvmrc" + node-version-file: '.nvmrc' cache: 'npm' cache-dependency-path: package-lock.json @@ -36,7 +36,7 @@ jobs: - name: Install node version of node-libcurl for inso tests run: node_modules/.bin/node-pre-gyp install --update-binary --directory node_modules/@getinsomnia/node-libcurl - - name : Build Inso + - name: Build Inso run: npm run build -w insomnia-inso - name: Run Inso unit tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d37db8fef..ee46f761b8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,9 +36,6 @@ jobs: - name: Lint run: npm run lint - - name: Lint markdown - run: npm run lint:markdown - - name: Type checks run: npm run type-check diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index b4acc52ae8..26401962d5 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -1,11 +1,10 @@ # .github/workflows/update-changelog.yaml -name: "Update Changelog" +name: 'Update Changelog' on: release: types: [released] - jobs: update: runs-on: ubuntu-22.04 diff --git a/.prettierrc b/.prettierrc index f4c7f69576..baa3b25924 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,9 +8,7 @@ { "files": "packages/insomnia/**/*", "options": { - "plugins": [ - "prettier-plugin-tailwindcss" - ] + "plugins": ["prettier-plugin-tailwindcss"] } } ] diff --git a/.vscode/launch.json b/.vscode/launch.json index dbb3af44aa..f3d1a6383b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -84,7 +84,7 @@ }, { "value": "help" - }, + } ] } ], @@ -98,10 +98,7 @@ }, "stopAll": true, "preLaunchTask": "Insomnia: Compile (Watch)", - "configurations": [ - "Electron: main", - "Electron: renderer" - ] + "configurations": ["Electron: main", "Electron: renderer"] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 87e55d10a5..96d0f3f7e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -43,5 +43,5 @@ ], "[cpp]": { "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" - }, + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ff868d6923..221e2b178f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,7 +11,7 @@ "options": { "cwd": "${workspaceFolder}/packages/insomnia", "env": { - "NODE_ENV": "development", + "NODE_ENV": "development" } }, "command": "${workspaceRoot}/node_modules/.bin/esr esbuild.main.ts" @@ -24,7 +24,7 @@ "options": { "cwd": "${workspaceFolder}/packages/insomnia", "env": { - "NODE_ENV": "development", + "NODE_ENV": "development" } }, "isBackground": true, @@ -57,10 +57,7 @@ { "label": "Insomnia: Compile (Watch)", "detail": "Compile Renderer (Watch) | Compile Main", - "dependsOn": [ - "Insomnia: Compile Renderer (Watch)", - "Insomnia: Compile Main" - ] + "dependsOn": ["Insomnia: Compile Renderer (Watch)", "Insomnia: Compile Main"] }, { "label": "Inso: Compile (Watch)", diff --git a/CHANGELOG.md b/CHANGELOG.md index 91861b6f1b..b78625bcc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,166 +8,170 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [core@10.0.0] - 2024-09-10 ## What's Changed -* bump: react-router by @CurryYangxx in https://github.com/Kong/insomnia/pull/7795 -* fix(Sync Staging Modal): use action to update selected items by @gatzjames in https://github.com/Kong/insomnia/pull/7794 -* Fixed copying credential In Auth: Basic were username and password input needed to copied by users by @pranavithape in https://github.com/Kong/insomnia/pull/7789 -* fix(ux): duplicate improvement by @CurryYangxx in https://github.com/Kong/insomnia/pull/7803 -* fixes lodash.set cve by @jackkav in https://github.com/Kong/insomnia/pull/7801 -* chore: rm userId from sentry [INS-4260] by @filfreire in https://github.com/Kong/insomnia/pull/7804 -* fix cves and add CI check by @jackkav in https://github.com/Kong/insomnia/pull/7806 -* Fix: Keep equal sign for empty query parameters[INS-4228] by @cwangsmv in https://github.com/Kong/insomnia/pull/7802 -* chore: hash userID on segment [INS-4260] by @filfreire in https://github.com/Kong/insomnia/pull/7805 -* Fix backslash in environment key freeze app [INS-4157] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7763 -* fix(Key-Value Editor): deleting the last item on the key-value pair not showing an empty pair by @gatzjames in https://github.com/Kong/insomnia/pull/7818 -* Remove styled-components by @jackkav in https://github.com/Kong/insomnia/pull/7809 -* feat(sync): support offline commit- [INS-4226] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7811 -* :rocket: 9.3.4-beta.0 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7823 -* Import postman env in Insomnia project level [INS-4253] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7821 -* feat: display uncommit&unpush change - [INS-4138] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7816 -* Preserve the original Authorization headers when importing [INS-4269] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7827 -* feat: Context menu for Nunjucks tag[INS-4273] by @cwangsmv in https://github.com/Kong/insomnia/pull/7828 -* feat(Keyboard Shorcuts): update delete request shortcut by @gatzjames in https://github.com/Kong/insomnia/pull/7824 -* feat: show uncommit&unpush status for all projects-[INS-4138] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7830 -* Fix: GraphQL request export curl body issue and GraphQL payload delete issue[INS-4281] by @cwangsmv in https://github.com/Kong/insomnia/pull/7831 -* :rocket: 9.3.4-beta.1 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7836 -* Try to fix smoke test flaky by @cwangsmv in https://github.com/Kong/insomnia/pull/7840 -* fix: test-util snippet to proper status code check [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7844 -* feat(Project View): UI improvements by @gatzjames in https://github.com/Kong/insomnia/pull/7850 -* fix: disable failure on npm audit [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7862 -* chore: bump electron to 30.4 [INS-4316] by @filfreire in https://github.com/Kong/insomnia/pull/7852 -* import postman data dump [INS-3810] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7834 -* support for removing default org project by @yaoweiprc in https://github.com/Kong/insomnia/pull/7854 -* fix: persist cookies from response together with ones from after-response script by @ihexxa in https://github.com/Kong/insomnia/pull/7819 -* chore: split packaging for windows builds [INS-3983] by @filfreire in https://github.com/Kong/insomnia/pull/7838 -* fix(Git Sync): fix issue when switching to Insomnia Sync by @gatzjames in https://github.com/Kong/insomnia/pull/7860 -* fix: handle login when opening org logged out [INS-4330] by @filfreire in https://github.com/Kong/insomnia/pull/7865 -* Revert "support for removing default org project" by @yaoweiprc in https://github.com/Kong/insomnia/pull/7874 -* inso cli scripting first pass by @jackkav in https://github.com/Kong/insomnia/pull/7790 -* Allow deleting default project in org and fix sync issue [INS-4342] [INS-4311] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7881 -* chore(runner): cleaning up runner-pr1 and resolve conflicts by @ihexxa in https://github.com/Kong/insomnia/pull/7878 -* :rocket: 10.0.0-beta.0 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7882 -* only minify inso cli in prod by @jackkav in https://github.com/Kong/insomnia/pull/7879 -* feat: Add logic to redirect users based on their plan when creating a new organization by @pavkout in https://github.com/Kong/insomnia/pull/7856 -* tabs should rerender when changing mock by @jackkav in https://github.com/Kong/insomnia/pull/7888 -* fix: check for open curl by @jackkav in https://github.com/Kong/insomnia/pull/7889 -* Bump/electron-31 by @jackkav in https://github.com/Kong/insomnia/pull/7884 -* shell.nix -> flake.nix by @jackkav in https://github.com/Kong/insomnia/pull/7892 -* add mock method header by @jackkav in https://github.com/Kong/insomnia/pull/7872 -* use nixpkgs/unstable by @jackkav in https://github.com/Kong/insomnia/pull/7902 -* fix: file not synced after switch sync method -[INS-4347] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7897 -* fix: rename untracked projects [INS-4365] by @filfreire in https://github.com/Kong/insomnia/pull/7898 -* fix: flaky git test by @ihexxa in https://github.com/Kong/insomnia/pull/7883 -* Add ut and e2e test for data upload and pre-script in collection runner by @cwangsmv in https://github.com/Kong/insomnia/pull/7903 -* Supporting moving files from one project to another [INS-3865] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7849 -* Enhance Response function to aware environment change and update description[INS-4279] by @cwangsmv in https://github.com/Kong/insomnia/pull/7896 -* chore: update AI service URL to use 'https://ai-helper.insomnia.rest' [INS-4367] by @pavkout in https://github.com/Kong/insomnia/pull/7910 -* fix(runner): some minor fixes and improvements by @ihexxa in https://github.com/Kong/insomnia/pull/7900 -* improve flakey test by @jackkav in https://github.com/Kong/insomnia/pull/7909 -* feat: sign all files on windows [INS-4362] by @filfreire in https://github.com/Kong/insomnia/pull/7913 -* Avoid encoding queryParams when request.settingEncodeUrl is set to false by @XSPGMike in https://github.com/Kong/insomnia/pull/7893 -* feat: improve EDN response by @garug in https://github.com/Kong/insomnia/pull/7777 -* fix: minor fixes in styles, linting and UT by @ihexxa in https://github.com/Kong/insomnia/pull/7916 -* preserve relationships in nunjucks tags by @jackkav in https://github.com/Kong/insomnia/pull/7915 -* :rocket: 10.0.0-beta.1 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7917 -* chore: cleanup after v10 beta.1 [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7918 -* feat(Onboarding): v10 by @gatzjames in https://github.com/Kong/insomnia/pull/7863 -* chore: return friendly message when sendRequest sees an error by @ihexxa in https://github.com/Kong/insomnia/pull/7911 -* chore: upgrade micromatch and add back npm audit by @ihexxa in https://github.com/Kong/insomnia/pull/7921 -* fix(runner): some minor fixes by @ihexxa in https://github.com/Kong/insomnia/pull/7923 -* add team check by @jackkav in https://github.com/Kong/insomnia/pull/7919 -* :rocket: 10.0.0-beta.2 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7924 -* Fix duplicate file cause application error in collection view UI by @cwangsmv in https://github.com/Kong/insomnia/pull/7928 -* :rocket: 10.0.0-beta.3 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7930 + +- bump: react-router by @CurryYangxx in https://github.com/Kong/insomnia/pull/7795 +- fix(Sync Staging Modal): use action to update selected items by @gatzjames in https://github.com/Kong/insomnia/pull/7794 +- Fixed copying credential In Auth: Basic were username and password input needed to copied by users by @pranavithape in https://github.com/Kong/insomnia/pull/7789 +- fix(ux): duplicate improvement by @CurryYangxx in https://github.com/Kong/insomnia/pull/7803 +- fixes lodash.set cve by @jackkav in https://github.com/Kong/insomnia/pull/7801 +- chore: rm userId from sentry [INS-4260] by @filfreire in https://github.com/Kong/insomnia/pull/7804 +- fix cves and add CI check by @jackkav in https://github.com/Kong/insomnia/pull/7806 +- Fix: Keep equal sign for empty query parameters[INS-4228] by @cwangsmv in https://github.com/Kong/insomnia/pull/7802 +- chore: hash userID on segment [INS-4260] by @filfreire in https://github.com/Kong/insomnia/pull/7805 +- Fix backslash in environment key freeze app [INS-4157] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7763 +- fix(Key-Value Editor): deleting the last item on the key-value pair not showing an empty pair by @gatzjames in https://github.com/Kong/insomnia/pull/7818 +- Remove styled-components by @jackkav in https://github.com/Kong/insomnia/pull/7809 +- feat(sync): support offline commit- [INS-4226] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7811 +- :rocket: 9.3.4-beta.0 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7823 +- Import postman env in Insomnia project level [INS-4253] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7821 +- feat: display uncommit&unpush change - [INS-4138] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7816 +- Preserve the original Authorization headers when importing [INS-4269] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7827 +- feat: Context menu for Nunjucks tag[INS-4273] by @cwangsmv in https://github.com/Kong/insomnia/pull/7828 +- feat(Keyboard Shorcuts): update delete request shortcut by @gatzjames in https://github.com/Kong/insomnia/pull/7824 +- feat: show uncommit&unpush status for all projects-[INS-4138] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7830 +- Fix: GraphQL request export curl body issue and GraphQL payload delete issue[INS-4281] by @cwangsmv in https://github.com/Kong/insomnia/pull/7831 +- :rocket: 9.3.4-beta.1 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7836 +- Try to fix smoke test flaky by @cwangsmv in https://github.com/Kong/insomnia/pull/7840 +- fix: test-util snippet to proper status code check [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7844 +- feat(Project View): UI improvements by @gatzjames in https://github.com/Kong/insomnia/pull/7850 +- fix: disable failure on npm audit [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7862 +- chore: bump electron to 30.4 [INS-4316] by @filfreire in https://github.com/Kong/insomnia/pull/7852 +- import postman data dump [INS-3810] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7834 +- support for removing default org project by @yaoweiprc in https://github.com/Kong/insomnia/pull/7854 +- fix: persist cookies from response together with ones from after-response script by @ihexxa in https://github.com/Kong/insomnia/pull/7819 +- chore: split packaging for windows builds [INS-3983] by @filfreire in https://github.com/Kong/insomnia/pull/7838 +- fix(Git Sync): fix issue when switching to Insomnia Sync by @gatzjames in https://github.com/Kong/insomnia/pull/7860 +- fix: handle login when opening org logged out [INS-4330] by @filfreire in https://github.com/Kong/insomnia/pull/7865 +- Revert "support for removing default org project" by @yaoweiprc in https://github.com/Kong/insomnia/pull/7874 +- inso cli scripting first pass by @jackkav in https://github.com/Kong/insomnia/pull/7790 +- Allow deleting default project in org and fix sync issue [INS-4342] [INS-4311] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7881 +- chore(runner): cleaning up runner-pr1 and resolve conflicts by @ihexxa in https://github.com/Kong/insomnia/pull/7878 +- :rocket: 10.0.0-beta.0 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7882 +- only minify inso cli in prod by @jackkav in https://github.com/Kong/insomnia/pull/7879 +- feat: Add logic to redirect users based on their plan when creating a new organization by @pavkout in https://github.com/Kong/insomnia/pull/7856 +- tabs should rerender when changing mock by @jackkav in https://github.com/Kong/insomnia/pull/7888 +- fix: check for open curl by @jackkav in https://github.com/Kong/insomnia/pull/7889 +- Bump/electron-31 by @jackkav in https://github.com/Kong/insomnia/pull/7884 +- shell.nix -> flake.nix by @jackkav in https://github.com/Kong/insomnia/pull/7892 +- add mock method header by @jackkav in https://github.com/Kong/insomnia/pull/7872 +- use nixpkgs/unstable by @jackkav in https://github.com/Kong/insomnia/pull/7902 +- fix: file not synced after switch sync method -[INS-4347] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7897 +- fix: rename untracked projects [INS-4365] by @filfreire in https://github.com/Kong/insomnia/pull/7898 +- fix: flaky git test by @ihexxa in https://github.com/Kong/insomnia/pull/7883 +- Add ut and e2e test for data upload and pre-script in collection runner by @cwangsmv in https://github.com/Kong/insomnia/pull/7903 +- Supporting moving files from one project to another [INS-3865] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7849 +- Enhance Response function to aware environment change and update description[INS-4279] by @cwangsmv in https://github.com/Kong/insomnia/pull/7896 +- chore: update AI service URL to use 'https://ai-helper.insomnia.rest' [INS-4367] by @pavkout in https://github.com/Kong/insomnia/pull/7910 +- fix(runner): some minor fixes and improvements by @ihexxa in https://github.com/Kong/insomnia/pull/7900 +- improve flakey test by @jackkav in https://github.com/Kong/insomnia/pull/7909 +- feat: sign all files on windows [INS-4362] by @filfreire in https://github.com/Kong/insomnia/pull/7913 +- Avoid encoding queryParams when request.settingEncodeUrl is set to false by @XSPGMike in https://github.com/Kong/insomnia/pull/7893 +- feat: improve EDN response by @garug in https://github.com/Kong/insomnia/pull/7777 +- fix: minor fixes in styles, linting and UT by @ihexxa in https://github.com/Kong/insomnia/pull/7916 +- preserve relationships in nunjucks tags by @jackkav in https://github.com/Kong/insomnia/pull/7915 +- :rocket: 10.0.0-beta.1 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7917 +- chore: cleanup after v10 beta.1 [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7918 +- feat(Onboarding): v10 by @gatzjames in https://github.com/Kong/insomnia/pull/7863 +- chore: return friendly message when sendRequest sees an error by @ihexxa in https://github.com/Kong/insomnia/pull/7911 +- chore: upgrade micromatch and add back npm audit by @ihexxa in https://github.com/Kong/insomnia/pull/7921 +- fix(runner): some minor fixes by @ihexxa in https://github.com/Kong/insomnia/pull/7923 +- add team check by @jackkav in https://github.com/Kong/insomnia/pull/7919 +- :rocket: 10.0.0-beta.2 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7924 +- Fix duplicate file cause application error in collection view UI by @cwangsmv in https://github.com/Kong/insomnia/pull/7928 +- :rocket: 10.0.0-beta.3 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7930 ## New Contributors -* @pranavithape made their first contribution in https://github.com/Kong/insomnia/pull/7789 -* @XSPGMike made their first contribution in https://github.com/Kong/insomnia/pull/7893 -* @garug made their first contribution in https://github.com/Kong/insomnia/pull/7777 + +- @pranavithape made their first contribution in https://github.com/Kong/insomnia/pull/7789 +- @XSPGMike made their first contribution in https://github.com/Kong/insomnia/pull/7893 +- @garug made their first contribution in https://github.com/Kong/insomnia/pull/7777 **Full Changelog**: https://github.com/Kong/insomnia/compare/core@9.3.3...core@10.0.0 ## [core@9.3.3] - 2024-07-31 ## What's Changed -* perf: App start improvement [INS-3957] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7492 -* :rocket: 9.3.3-beta.0 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7674 -* fix: default user-agent for oauth2 [7672] by @filfreire in https://github.com/Kong/insomnia/pull/7675 -* inso fifth pass by @jackkav in https://github.com/Kong/insomnia/pull/7601 -* feat: inso parent folder auth by @jackkav in https://github.com/Kong/insomnia/pull/7676 -* chore: duplicate / symbol for import in insomnia-scripting-environment/src/objects/(interfaces.ts, request.ts) by @Novsochetra in https://github.com/Kong/insomnia/pull/7686 -* fix: changelog [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7677 -* chore: enable sentry tracing by @CurryYangxx in https://github.com/Kong/insomnia/pull/7688 -* force vite to always wipe cache by @jackkav in https://github.com/Kong/insomnia/pull/7690 -* chore: make the overlay darker in displaying request timings by @ihexxa in https://github.com/Kong/insomnia/pull/7691 -* split test job into app and cli by @jackkav in https://github.com/Kong/insomnia/pull/7685 -* chore: add smoke test for git-sync [INS-4132] by @filfreire in https://github.com/Kong/insomnia/pull/7682 -* feat(Markdown Preview): always enable preview by @gatzjames in https://github.com/Kong/insomnia/pull/7694 -* fix(Delete Environment): Don't show empty view when deleting an environment by @gatzjames in https://github.com/Kong/insomnia/pull/7695 -* fix(Export): Option to export all data from the settings on the login view by @gatzjames in https://github.com/Kong/insomnia/pull/7702 -* fix: typo in style name by @ihexxa in https://github.com/Kong/insomnia/pull/7701 -* fix: ui improvement when return deferred data in loader by @CurryYangxx in https://github.com/Kong/insomnia/pull/7681 -* fix: refresh storage rule when org change by @CurryYangxx in https://github.com/Kong/insomnia/pull/7707 -* hide self host url in create/edit mock by @jackkav in https://github.com/Kong/insomnia/pull/7704 -* feat(Request pane): Add indicators for body and auth in the request pane tabs by @gatzjames in https://github.com/Kong/insomnia/pull/7697 -* Trim Bearer Authentication Strings by @SimplexShotz in https://github.com/Kong/insomnia/pull/7279 -* feat: show deprecation warnings on graphql arguments by @anujbiyani in https://github.com/Kong/insomnia/pull/7364 -* Clean up outdate jest and tsconfigs by @jackkav in https://github.com/Kong/insomnia/pull/7712 -* chore: mv prerelease tests into smoke [INS-4132] by @filfreire in https://github.com/Kong/insomnia/pull/7705 -* feat: add test utils on scripting snippets [INS-4141] by @filfreire in https://github.com/Kong/insomnia/pull/7692 -* fix: migrate loader redirect by @CurryYangxx in https://github.com/Kong/insomnia/pull/7426 -* refactor: flatten and reduce tsconfigs by @jackkav in https://github.com/Kong/insomnia/pull/7716 -* enable verbatimModuleSyntax by @jackkav in https://github.com/Kong/insomnia/pull/7718 -* perf: return deferred data in permission loader by @CurryYangxx in https://github.com/Kong/insomnia/pull/7635 -* perf: change org performance improvement [INS-3968] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7582 -* feat: add async task indicator [INS-4106] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7522 -* fix(sdk): sdk type cleanup by @ihexxa in https://github.com/Kong/insomnia/pull/7721 -* chore: bump electron 30.0 to 30.2 by @filfreire in https://github.com/Kong/insomnia/pull/7714 -* fix: Use SSE for storage control updates by @pavkout in https://github.com/Kong/insomnia/pull/7661 -* :rocket: 9.3.3-beta.1 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7723 -* Update CHANGELOG.md by @CurryYangxx in https://github.com/Kong/insomnia/pull/7725 -* feat: inso collection runner by @jackkav in https://github.com/Kong/insomnia/pull/7700 -* fix: cannot delete request by shortcut [INS-4156] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7728 -* fix(Key-Value Editor): Edit mode by @gatzjames in https://github.com/Kong/insomnia/pull/7739 -* remove send-request by @jackkav in https://github.com/Kong/insomnia/pull/7731 -* feat(Generate Collection from Spec): add description to requests if it's available from the oas3 schema by @gatzjames in https://github.com/Kong/insomnia/pull/7734 -* fix: syncing status indicator ui by @CurryYangxx in https://github.com/Kong/insomnia/pull/7730 -* feat(Sidebar): interactions improvements by @gatzjames in https://github.com/Kong/insomnia/pull/7722 -* fix(Git Clone): redirect using incorrect organizationId by @gatzjames in https://github.com/Kong/insomnia/pull/7740 -* :rocket: 9.3.3-beta.2 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7741 -* handle null auth by @jackkav in https://github.com/Kong/insomnia/pull/7746 -* fix(Collection): Clean up auto-scroll and add back selected item styling by @gatzjames in https://github.com/Kong/insomnia/pull/7747 -* fixes incorrect scrollbar display issue by @Karthik7406 in https://github.com/Kong/insomnia/pull/7742 -* fix: reduce uncessary navigate when switching requests and tests by @CurryYangxx in https://github.com/Kong/insomnia/pull/7748 -* fix: lost in header transforming and blank req body by @ihexxa in https://github.com/Kong/insomnia/pull/7738 -* chore: git sync pull push test [INS-4132] by @filfreire in https://github.com/Kong/insomnia/pull/7720 -* vitest by @jackkav in https://github.com/Kong/insomnia/pull/7754 -* feat(History): Navigate to last opened workspace on app load by @gatzjames in https://github.com/Kong/insomnia/pull/7755 -* feat(Sentry): clean up unnecessary sentry stack by @gatzjames in https://github.com/Kong/insomnia/pull/7758 -* fix(Settings): update header styles for analytics by @gatzjames in https://github.com/Kong/insomnia/pull/7759 -* fix: can't match project when last visit page is dashboard by @CurryYangxx in https://github.com/Kong/insomnia/pull/7762 -* fix(History): navigate to the project if the last visited workspace has been deleted by @gatzjames in https://github.com/Kong/insomnia/pull/7764 -* feat(Response Pane): improve tabs styles by @gatzjames in https://github.com/Kong/insomnia/pull/7765 -* :rocket: 9.3.3-beta.3 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7766 -* chore: add sentry metric [INS-4115] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7727 -* Vitest-2 app package by @jackkav in https://github.com/Kong/insomnia/pull/7757 -* fix: project switch report by @CurryYangxx in https://github.com/Kong/insomnia/pull/7771 -* :rocket: 9.3.3-beta.4 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7774 -* feat(UI): UI improvements for the app by @gatzjames in https://github.com/Kong/insomnia/pull/7773 -* chore: check analytics issue [INS-4212] by @filfreire in https://github.com/Kong/insomnia/pull/7775 -* fix(GraphQL Editor): make inputValueDeprecation optional and change variable mode to json by @gatzjames in https://github.com/Kong/insomnia/pull/7779 -* inso cli dx improvements by @jackkav in https://github.com/Kong/insomnia/pull/7776 -* fix(Git Staging Modal): close the modal on ESC by @gatzjames in https://github.com/Kong/insomnia/pull/7781 -* fix(KeyValue Editor): fix key value focus issue and handle updating params from url by @gatzjames in https://github.com/Kong/insomnia/pull/7780 -* feat(Styles): Minor style improvements by @gatzjames in https://github.com/Kong/insomnia/pull/7782 -* :rocket: 9.3.3-beta.5 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7783 -* Add type checking to sdk package by @jackkav in https://github.com/Kong/insomnia/pull/7719 + +- perf: App start improvement [INS-3957] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7492 +- :rocket: 9.3.3-beta.0 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7674 +- fix: default user-agent for oauth2 [7672] by @filfreire in https://github.com/Kong/insomnia/pull/7675 +- inso fifth pass by @jackkav in https://github.com/Kong/insomnia/pull/7601 +- feat: inso parent folder auth by @jackkav in https://github.com/Kong/insomnia/pull/7676 +- chore: duplicate / symbol for import in insomnia-scripting-environment/src/objects/(interfaces.ts, request.ts) by @Novsochetra in https://github.com/Kong/insomnia/pull/7686 +- fix: changelog [no-ticket] by @filfreire in https://github.com/Kong/insomnia/pull/7677 +- chore: enable sentry tracing by @CurryYangxx in https://github.com/Kong/insomnia/pull/7688 +- force vite to always wipe cache by @jackkav in https://github.com/Kong/insomnia/pull/7690 +- chore: make the overlay darker in displaying request timings by @ihexxa in https://github.com/Kong/insomnia/pull/7691 +- split test job into app and cli by @jackkav in https://github.com/Kong/insomnia/pull/7685 +- chore: add smoke test for git-sync [INS-4132] by @filfreire in https://github.com/Kong/insomnia/pull/7682 +- feat(Markdown Preview): always enable preview by @gatzjames in https://github.com/Kong/insomnia/pull/7694 +- fix(Delete Environment): Don't show empty view when deleting an environment by @gatzjames in https://github.com/Kong/insomnia/pull/7695 +- fix(Export): Option to export all data from the settings on the login view by @gatzjames in https://github.com/Kong/insomnia/pull/7702 +- fix: typo in style name by @ihexxa in https://github.com/Kong/insomnia/pull/7701 +- fix: ui improvement when return deferred data in loader by @CurryYangxx in https://github.com/Kong/insomnia/pull/7681 +- fix: refresh storage rule when org change by @CurryYangxx in https://github.com/Kong/insomnia/pull/7707 +- hide self host url in create/edit mock by @jackkav in https://github.com/Kong/insomnia/pull/7704 +- feat(Request pane): Add indicators for body and auth in the request pane tabs by @gatzjames in https://github.com/Kong/insomnia/pull/7697 +- Trim Bearer Authentication Strings by @SimplexShotz in https://github.com/Kong/insomnia/pull/7279 +- feat: show deprecation warnings on graphql arguments by @anujbiyani in https://github.com/Kong/insomnia/pull/7364 +- Clean up outdate jest and tsconfigs by @jackkav in https://github.com/Kong/insomnia/pull/7712 +- chore: mv prerelease tests into smoke [INS-4132] by @filfreire in https://github.com/Kong/insomnia/pull/7705 +- feat: add test utils on scripting snippets [INS-4141] by @filfreire in https://github.com/Kong/insomnia/pull/7692 +- fix: migrate loader redirect by @CurryYangxx in https://github.com/Kong/insomnia/pull/7426 +- refactor: flatten and reduce tsconfigs by @jackkav in https://github.com/Kong/insomnia/pull/7716 +- enable verbatimModuleSyntax by @jackkav in https://github.com/Kong/insomnia/pull/7718 +- perf: return deferred data in permission loader by @CurryYangxx in https://github.com/Kong/insomnia/pull/7635 +- perf: change org performance improvement [INS-3968] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7582 +- feat: add async task indicator [INS-4106] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7522 +- fix(sdk): sdk type cleanup by @ihexxa in https://github.com/Kong/insomnia/pull/7721 +- chore: bump electron 30.0 to 30.2 by @filfreire in https://github.com/Kong/insomnia/pull/7714 +- fix: Use SSE for storage control updates by @pavkout in https://github.com/Kong/insomnia/pull/7661 +- :rocket: 9.3.3-beta.1 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7723 +- Update CHANGELOG.md by @CurryYangxx in https://github.com/Kong/insomnia/pull/7725 +- feat: inso collection runner by @jackkav in https://github.com/Kong/insomnia/pull/7700 +- fix: cannot delete request by shortcut [INS-4156] by @yaoweiprc in https://github.com/Kong/insomnia/pull/7728 +- fix(Key-Value Editor): Edit mode by @gatzjames in https://github.com/Kong/insomnia/pull/7739 +- remove send-request by @jackkav in https://github.com/Kong/insomnia/pull/7731 +- feat(Generate Collection from Spec): add description to requests if it's available from the oas3 schema by @gatzjames in https://github.com/Kong/insomnia/pull/7734 +- fix: syncing status indicator ui by @CurryYangxx in https://github.com/Kong/insomnia/pull/7730 +- feat(Sidebar): interactions improvements by @gatzjames in https://github.com/Kong/insomnia/pull/7722 +- fix(Git Clone): redirect using incorrect organizationId by @gatzjames in https://github.com/Kong/insomnia/pull/7740 +- :rocket: 9.3.3-beta.2 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7741 +- handle null auth by @jackkav in https://github.com/Kong/insomnia/pull/7746 +- fix(Collection): Clean up auto-scroll and add back selected item styling by @gatzjames in https://github.com/Kong/insomnia/pull/7747 +- fixes incorrect scrollbar display issue by @Karthik7406 in https://github.com/Kong/insomnia/pull/7742 +- fix: reduce uncessary navigate when switching requests and tests by @CurryYangxx in https://github.com/Kong/insomnia/pull/7748 +- fix: lost in header transforming and blank req body by @ihexxa in https://github.com/Kong/insomnia/pull/7738 +- chore: git sync pull push test [INS-4132] by @filfreire in https://github.com/Kong/insomnia/pull/7720 +- vitest by @jackkav in https://github.com/Kong/insomnia/pull/7754 +- feat(History): Navigate to last opened workspace on app load by @gatzjames in https://github.com/Kong/insomnia/pull/7755 +- feat(Sentry): clean up unnecessary sentry stack by @gatzjames in https://github.com/Kong/insomnia/pull/7758 +- fix(Settings): update header styles for analytics by @gatzjames in https://github.com/Kong/insomnia/pull/7759 +- fix: can't match project when last visit page is dashboard by @CurryYangxx in https://github.com/Kong/insomnia/pull/7762 +- fix(History): navigate to the project if the last visited workspace has been deleted by @gatzjames in https://github.com/Kong/insomnia/pull/7764 +- feat(Response Pane): improve tabs styles by @gatzjames in https://github.com/Kong/insomnia/pull/7765 +- :rocket: 9.3.3-beta.3 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7766 +- chore: add sentry metric [INS-4115] by @CurryYangxx in https://github.com/Kong/insomnia/pull/7727 +- Vitest-2 app package by @jackkav in https://github.com/Kong/insomnia/pull/7757 +- fix: project switch report by @CurryYangxx in https://github.com/Kong/insomnia/pull/7771 +- :rocket: 9.3.3-beta.4 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7774 +- feat(UI): UI improvements for the app by @gatzjames in https://github.com/Kong/insomnia/pull/7773 +- chore: check analytics issue [INS-4212] by @filfreire in https://github.com/Kong/insomnia/pull/7775 +- fix(GraphQL Editor): make inputValueDeprecation optional and change variable mode to json by @gatzjames in https://github.com/Kong/insomnia/pull/7779 +- inso cli dx improvements by @jackkav in https://github.com/Kong/insomnia/pull/7776 +- fix(Git Staging Modal): close the modal on ESC by @gatzjames in https://github.com/Kong/insomnia/pull/7781 +- fix(KeyValue Editor): fix key value focus issue and handle updating params from url by @gatzjames in https://github.com/Kong/insomnia/pull/7780 +- feat(Styles): Minor style improvements by @gatzjames in https://github.com/Kong/insomnia/pull/7782 +- :rocket: 9.3.3-beta.5 by @insomnia-infra in https://github.com/Kong/insomnia/pull/7783 +- Add type checking to sdk package by @jackkav in https://github.com/Kong/insomnia/pull/7719 ## New Contributors -* @Novsochetra made their first contribution in https://github.com/Kong/insomnia/pull/7686 -* @SimplexShotz made their first contribution in https://github.com/Kong/insomnia/pull/7279 -* @anujbiyani made their first contribution in https://github.com/Kong/insomnia/pull/7364 -* @yaoweiprc made their first contribution in https://github.com/Kong/insomnia/pull/7728 -* @Karthik7406 made their first contribution in https://github.com/Kong/insomnia/pull/7742 + +- @Novsochetra made their first contribution in https://github.com/Kong/insomnia/pull/7686 +- @SimplexShotz made their first contribution in https://github.com/Kong/insomnia/pull/7279 +- @anujbiyani made their first contribution in https://github.com/Kong/insomnia/pull/7364 +- @yaoweiprc made their first contribution in https://github.com/Kong/insomnia/pull/7728 +- @Karthik7406 made their first contribution in https://github.com/Kong/insomnia/pull/7742 **Full Changelog**: https://github.com/Kong/insomnia/compare/core@9.3.2...core@9.3.3 @@ -252,7 +256,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix(Response Tabs): Tabs with a menu inside are not accessible - Response Panes by @gatzjames in - fix: script to parse binary digests by @saisatishkarra in - :rocket: 9.3.0-alpha.5 by @insomnia-infra in -- feat: folder inheritance scripts by @jackkav in +- feat: folder inheritance scripts by @jackkav in - fix: use base64 output file for provenance for large assets by @saisatishkarra in - Bump/electron-30 by @jackkav in - chore: new analytics events [INS-3938] by @filfreire in @@ -276,7 +280,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: url preview should contain auth params by @ihexxa in - include read only in headers count by @jackkav in - remove oas 2 kong by @jackkav in -- fix: windows artifact and update code signer [INS-3993][INS-3982] by @filfreire in +- fix: windows artifact and update code signer [INS-3993][ins-3982] by @filfreire in - :rocket: 9.3.0-alpha.11 by @insomnia-infra in - :rocket: 9.3.0-beta.3 by @insomnia-infra in - chore(Minor UI improvements): Expand/Collapse all and file card titles by @gatzjames in @@ -439,11 +443,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat(hidden-window): enable baseEnvironment in the pre-request scripting [INS-3379] by @ihexxa in - fix: release-start changelog step [no-ticket] by @filfreire in - fix: move changelog step to release-publish by @filfreire in -- feat: enable globals, iterationData and variables in pre-request scripting [INS-3379] by @ihexxa in +- feat: enable globals, iterationData and variables in pre-request scripting [INS-3379] by @ihexxa in - chore: bump GH actions versions [no-ticket] by @filfreire in - feat(Insomnia Cloud Sync): Update filesystem driver for VCS sync by @gatzjames in - feat: enable property in pre-request scripting [INS-3379] by @ihexxa in -- feat: enable headers in pre-request scripting [INS-3379] by @ihexxa in +- feat: enable headers in pre-request scripting [INS-3379] by @ihexxa in - feat: enable collection-variable in pre-request scripting [INS-3379] by @ihexxa in - feat: enable Url in pre-request scripting [INS-3379] by @ihexxa in - feat: enable Request and Response in pre-request scripting [INS-3379] by @ihexxa in @@ -571,19 +575,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### :sparkles: New Features -- [`e1e3b13`](https://github.com/Kong/insomnia/commit/e1e3b139b3bb917ab9dfcb0ce12d16079dee9c04) - **unit-tests**: Unit test reordering *(PR [#7020](https://github.com/Kong/insomnia/pull/7020) by [@gatzjames](https://github.com/gatzjames))* -- [`2249bb7`](https://github.com/Kong/insomnia/commit/2249bb7b98c947ab1cb11955928fd80d4adec845) - **environment**: update environments icons *(PR [#7050](https://github.com/Kong/insomnia/pull/7050) by [@gatzjames](https://github.com/gatzjames))* -- [`a09c233`](https://github.com/Kong/insomnia/commit/a09c23305c9c493105808b8df23d1911f5b59ea2) - **pane-tabs**: Consistent styles for tabs *(PR [#7062](https://github.com/Kong/insomnia/pull/7062) by [@gatzjames](https://github.com/gatzjames))* -- [`d1c2928`](https://github.com/Kong/insomnia/commit/d1c292891cc9dd8a17d4637f643336cf1afcccfa) - **command-palette**: add button to open the command palette *(PR [#7064](https://github.com/Kong/insomnia/pull/7064) by [@gatzjames](https://github.com/gatzjames))* +- [`e1e3b13`](https://github.com/Kong/insomnia/commit/e1e3b139b3bb917ab9dfcb0ce12d16079dee9c04) - **unit-tests**: Unit test reordering _(PR [#7020](https://github.com/Kong/insomnia/pull/7020) by [@gatzjames](https://github.com/gatzjames))_ +- [`2249bb7`](https://github.com/Kong/insomnia/commit/2249bb7b98c947ab1cb11955928fd80d4adec845) - **environment**: update environments icons _(PR [#7050](https://github.com/Kong/insomnia/pull/7050) by [@gatzjames](https://github.com/gatzjames))_ +- [`a09c233`](https://github.com/Kong/insomnia/commit/a09c23305c9c493105808b8df23d1911f5b59ea2) - **pane-tabs**: Consistent styles for tabs _(PR [#7062](https://github.com/Kong/insomnia/pull/7062) by [@gatzjames](https://github.com/gatzjames))_ +- [`d1c2928`](https://github.com/Kong/insomnia/commit/d1c292891cc9dd8a17d4637f643336cf1afcccfa) - **command-palette**: add button to open the command palette _(PR [#7064](https://github.com/Kong/insomnia/pull/7064) by [@gatzjames](https://github.com/gatzjames))_ ### :bug: Bug Fixes -- [`df0a791`](https://github.com/Kong/insomnia/commit/df0a79194143dc615310ecc0976381c538f695f2) - re-initialize the parameter editor state when switching between requests *(PR [#7005](https://github.com/Kong/insomnia/pull/7005) by [@gatzjames](https://github.com/gatzjames))* - - :arrow_lower_right: *fixes issue [#7000](undefined) opened by [@jwarner112](https://github.com/jwarner112)* -- [`3fceccf`](https://github.com/Kong/insomnia/commit/3fceccfdf691a0f3d7592f31120030eeff92be61) - **workspace**: Add default name for when creating a workspace *(PR [#7046](https://github.com/Kong/insomnia/pull/7046) by [@gatzjames](https://github.com/gatzjames))* +- [`df0a791`](https://github.com/Kong/insomnia/commit/df0a79194143dc615310ecc0976381c538f695f2) - re-initialize the parameter editor state when switching between requests _(PR [#7005](https://github.com/Kong/insomnia/pull/7005) by [@gatzjames](https://github.com/gatzjames))_ + - :arrow*lower_right: \_fixes issue [#7000](undefined) opened by [@jwarner112](https://github.com/jwarner112)* +- [`3fceccf`](https://github.com/Kong/insomnia/commit/3fceccfdf691a0f3d7592f31120030eeff92be61) - **workspace**: Add default name for when creating a workspace _(PR [#7046](https://github.com/Kong/insomnia/pull/7046) by [@gatzjames](https://github.com/gatzjames))_ ### :wrench: Chores -- [`353780e`](https://github.com/Kong/insomnia/commit/353780e16ab30853ce206398850c0c0f1c9bd887) - edit changelog process [INS-3456] *(PR [#7001](https://github.com/Kong/insomnia/pull/7001) by [@filfreire](https://github.com/filfreire))* +- [`353780e`](https://github.com/Kong/insomnia/commit/353780e16ab30853ce206398850c0c0f1c9bd887) - edit changelog process [INS-3456] _(PR [#7001](https://github.com/Kong/insomnia/pull/7001) by [@filfreire](https://github.com/filfreire))_ [core@8.6.1]: https://github.com/Kong/insomnia/compare/core@8.6.0...core@8.6.1 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c994585032..eff76a0cf2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -9,7 +9,7 @@ This code of conduct applies to all spaces managed by the Insomnia project, incl If you believe someone is violating the code of conduct, we ask that you report it by emailing [support@insomnia.rest](mailto:support@insomnia.rest). For more details please see our Reporting Guidelines - **Be friendly and patient.** -- **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. +- **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. - **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. - **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It's important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the Insomnia community should be respectful when dealing with other members as well as with people outside the Insomnia community. - **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior isn't acceptable. This includes, but is not limited to: diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index cdc91f24f2..d0ff1a328f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -25,7 +25,7 @@ Insomnia uses [`npm workspaces`](https://docs.npmjs.com/cli/v9/using-npm/workspa Insomnia Inso CLI is built using a series of steps -1. `insomnia-inso` uses monorepo references to import `insomnia` and `insomnia-testing` to expose `getSendRequestCallbackMemDb` and `generate`, `runTests`, `runTestsCli` respectively +1. `insomnia-inso` uses monorepo references to import `insomnia` and `insomnia-testing` to expose `getSendRequestCallbackMemDb` and `generate`, `runTests`, `runTestsCli` respectively 1. `packages/insomnia-inso/dist/index.js` is transpiled with esbuild to commonjs 1. `packages/insomnia-inso/bin/inso` is shell script which points at `packages/insomnia-inso/dist/index.js` and is used for local development 1. `packages/insomnia-inso/binaries/inso` is an executable made with `pkg` diff --git a/eslint.config.mjs b/eslint.config.mjs index 49f10f4a30..c274515823 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,5 @@ import eslint from '@eslint/js'; -import eslintConfigPrettier from "eslint-config-prettier/flat"; +import eslintConfigPrettier from 'eslint-config-prettier/flat'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort'; @@ -27,10 +27,10 @@ export default tseslint.config( 'eqeqeq': ['error', 'smart'], 'no-async-promise-executor': 'off', 'no-else-return': 'error', - 'no-empty': ["error", { "allowEmptyCatch": true }], + 'no-empty': ['error', { allowEmptyCatch: true }], 'no-var': 'error', 'no-trailing-spaces': 'error', - 'no-multiple-empty-lines': ['error', { 'max': 1, 'maxEOF': 0 }], + 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], 'no-inner-declarations': 'off', 'no-useless-escape': 'off', // TODO: Enable this rule 'object-curly-spacing': ['error', 'always'], @@ -43,26 +43,33 @@ export default tseslint.config( 'react/jsx-uses-vars': 'error', 'react/jsx-indent-props': ['error', 2], 'react/prop-types': 'off', - 'react/function-component-definition': ['error', { - 'namedComponents': 'arrow-function', - 'unnamedComponents': 'arrow-function', - }], + 'react/function-component-definition': [ + 'error', + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function', + }, + ], 'react/jsx-closing-bracket-location': ['error', 'line-aligned'], 'react/prefer-stateless-function': 'error', - 'react/jsx-key': ['error', { 'checkFragmentShorthand': true }], + 'react/jsx-key': ['error', { checkFragmentShorthand: true }], 'react/no-array-index-key': 'error', 'react/self-closing-comp': 'error', - 'react-hooks/exhaustive-deps': ['error', { - // From react-use https://github.com/streamich/react-use/issues/1703#issuecomment-770972824 - 'additionalHooks': '^use(Async|AsyncFn|AsyncRetry|Debounce|UpdateEffect|IsomorphicLayoutEffect|DeepCompareEffect|ShallowCompareEffect)$', - }], + 'react-hooks/exhaustive-deps': [ + 'error', + { + // From react-use https://github.com/streamich/react-use/issues/1703#issuecomment-770972824 + additionalHooks: + '^use(Async|AsyncFn|AsyncRetry|Debounce|UpdateEffect|IsomorphicLayoutEffect|DeepCompareEffect|ShallowCompareEffect)$', + }, + ], 'react-hooks/rules-of-hooks': 'error', '@typescript-eslint/array-type': ['error', { default: 'array', readonly: 'array' }], '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], '@typescript-eslint/consistent-type-imports': 'error', - '@typescript-eslint/no-empty-interface': ['error', { 'allowSingleExtends': true }], + '@typescript-eslint/no-empty-interface': ['error', { allowSingleExtends: true }], '@typescript-eslint/no-empty-object-type': 'off', // TODO: Enable this rule '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-namespace': ['error', { allowDeclarations: true }], @@ -77,7 +84,7 @@ export default tseslint.config( '@typescript-eslint/no-dynamic-delete': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-invalid-void-type': 'off', - } + }, }, eslintConfigPrettier, { @@ -106,6 +113,6 @@ export default tseslint.config( '**/traces/*', '**/verify-pkg.js', '**/__mocks__/*', - ] - } + ], + }, ); diff --git a/package.json b/package.json index 7f2209dd45..e2c79976b3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "lint": "npm run lint --workspaces --if-present", "type-check": "npm run type-check --workspaces --if-present", "test": "npm run test --workspaces --if-present", - "lint:markdown": "npx markdownlint-cli2 \"**/*.md\" \"#**/node_modules\" \"#**/CHANGELOG.md\"", "clean": "git clean -dfX", "install-libcurl-electron": "node_modules/.bin/node-pre-gyp install --directory node_modules/@getinsomnia/node-libcurl --update-binary --runtime=electron --target=$target", "inso-start": "npm start -w insomnia-inso", diff --git a/packages/insomnia-inso/README.md b/packages/insomnia-inso/README.md index 14b4326491..15921dcd4e 100644 --- a/packages/insomnia-inso/README.md +++ b/packages/insomnia-inso/README.md @@ -33,8 +33,7 @@ $PWD/packages/insomnia-inso/bin/inso -w packages/insomnia-inso/src/db/fixtures/g ### node-libcurl -`Error: The module '.../insomnia/node_modules/@getinsomnia/node-libcurl/lib/binding/node_libcurl.node' -was compiled against a different Node.js version using` +`Error: The module '.../insomnia/node_modules/@getinsomnia/node-libcurl/lib/binding/node_libcurl.node' was compiled against a different Node.js version using` node-libcurl builds for 3 operating systems and two versions of nodejs. insomnia-inso uses the nodejs build and insomnia app uses the electron build. you can switch between them using the following two commands diff --git a/packages/insomnia-inso/esbuild.ts b/packages/insomnia-inso/esbuild.ts index b249fc2875..c0fbf6a6fe 100644 --- a/packages/insomnia-inso/esbuild.ts +++ b/packages/insomnia-inso/esbuild.ts @@ -22,9 +22,7 @@ const config: BuildOptions = { paths: [args.resolveDir], }); // Call twice the replace is to solve the problem of the path in Windows - const pathEsm = pathUmdMay - .replace('/umd/', '/esm/') - .replace('\\umd\\', '\\esm\\'); + const pathEsm = pathUmdMay.replace('/umd/', '/esm/').replace('\\umd\\', '\\esm\\'); return { path: pathEsm }; }); }, diff --git a/packages/insomnia-inso/src/cli.test.ts b/packages/insomnia-inso/src/cli.test.ts index 6701238dc9..948447a07b 100644 --- a/packages/insomnia-inso/src/cli.test.ts +++ b/packages/insomnia-inso/src/cli.test.ts @@ -82,7 +82,8 @@ describe('inso dev bundle', () => { }); describe('response and timeline has scripting effects', () => { it('console log appears in timeline', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/minimal.yml wrk_5b5ab6 --verbose'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/minimal.yml wrk_5b5ab6 --verbose'; const result = await runCliFromRoot(input); if (result.code !== 0) { console.log(result); @@ -93,7 +94,8 @@ describe('inso dev bundle', () => { }); it('insomnia.request.addHeader works', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/script-add-header.yml wrk_5b5ab6 --verbose'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/script-add-header.yml wrk_5b5ab6 --verbose'; const result = await runCliFromRoot(input); if (result.code !== 0) { console.log(result); @@ -104,7 +106,8 @@ describe('inso dev bundle', () => { }); it('require("insomnia-collection") works', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/script-require.yml wrk_5b5ab6 --verbose'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/script-require.yml wrk_5b5ab6 --verbose'; const result = await runCliFromRoot(input); if (result.code !== 0) { console.log(result); @@ -114,7 +117,8 @@ describe('inso dev bundle', () => { }); it('insomnia.sendRequest works', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/script-send-request.yml wrk_cfacae --verbose'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/script-send-request.yml wrk_cfacae --verbose'; const result = await runCliFromRoot(input); if (result.code !== 0) { console.log(result); @@ -123,7 +127,8 @@ describe('inso dev bundle', () => { }); it('iterationData and iterationCount args work', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -d packages/insomnia-smoke-test/fixtures/files/runner-data.json -w packages/insomnia-inso/src/examples/three-requests.yml -n 2 -i req_3fd28aabbb18447abab1f45e6ee4bdc1 -e env_86e135 wrk_c992d40 --verbose'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -d packages/insomnia-smoke-test/fixtures/files/runner-data.json -w packages/insomnia-inso/src/examples/three-requests.yml -n 2 -i req_3fd28aabbb18447abab1f45e6ee4bdc1 -e env_86e135 wrk_c992d40 --verbose'; const result = await runCliFromRoot(input); if (result.code !== 0) { console.log(result); @@ -133,7 +138,8 @@ describe('inso dev bundle', () => { }); it('send request with client cert and key', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/db/fixtures/nedb --requestNamePattern "withCertAndCA" --verbose "Insomnia Designer" wrk_0b96eff'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/db/fixtures/nedb --requestNamePattern "withCertAndCA" --verbose "Insomnia Designer" wrk_0b96eff'; const result = await runCliFromRoot(input); if (result.code !== 0) { console.log(result); @@ -143,13 +149,15 @@ describe('inso dev bundle', () => { }); it('send request with settings enabled (by testing followRedirects)', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/db/fixtures/nedb --requestNamePattern "withSettings" --verbose "Insomnia Designer" wrk_0b96eff'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/db/fixtures/nedb --requestNamePattern "withSettings" --verbose "Insomnia Designer" wrk_0b96eff'; const result = await runCliFromRoot(input); expect(result.stdout).not.toContain("Issue another request to this URL: 'https://insomnia.rest/'"); }); it('run collection: run requests in specified order', async () => { - const input = '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/three-requests.yml -i req_6063adcdab5b409e9b4f00f47322df4a -i req_3fd28aabbb18447abab1f45e6ee4bdc1 -e env_86e135 wrk_c992d40 --verbose'; + const input = + '$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/three-requests.yml -i req_6063adcdab5b409e9b4f00f47322df4a -i req_3fd28aabbb18447abab1f45e6ee4bdc1 -e env_86e135 wrk_c992d40 --verbose'; const result = await runCliFromRoot(input); expect(result.code).toBe(0); @@ -164,8 +172,12 @@ describe('inso dev bundle', () => { }); }); -const packagedSuccessCodes = shouldReturnSuccessCode.map(x => x.replace('$PWD/packages/insomnia-inso/bin/inso', '$PWD/packages/insomnia-inso/binaries/inso')); -const packagedErrorCodes = shouldReturnErrorCode.map(x => x.replace('$PWD/packages/insomnia-inso/bin/inso', '$PWD/packages/insomnia-inso/binaries/inso')); +const packagedSuccessCodes = shouldReturnSuccessCode.map(x => + x.replace('$PWD/packages/insomnia-inso/bin/inso', '$PWD/packages/insomnia-inso/binaries/inso'), +); +const packagedErrorCodes = shouldReturnErrorCode.map(x => + x.replace('$PWD/packages/insomnia-inso/bin/inso', '$PWD/packages/insomnia-inso/binaries/inso'), +); describe('inso packaged binary', () => { describe('exit codes are consistent', () => { @@ -206,7 +218,12 @@ describe('Snapshot for', () => { }); // Execute the command in the root directory of the project -export const runCliFromRoot = (input: string): Promise<{ code: number; error: ExecException | null; stdout: string; stderr: string }> => { - return new Promise(resolve => exec(input, { cwd: path.resolve(__dirname, '../../..') }, - (error, stdout, stderr) => resolve({ code: error?.code || 0, error, stdout, stderr }))); +export const runCliFromRoot = ( + input: string, +): Promise<{ code: number; error: ExecException | null; stdout: string; stderr: string }> => { + return new Promise(resolve => + exec(input, { cwd: path.resolve(__dirname, '../../..') }, (error, stdout, stderr) => + resolve({ code: error?.code || 0, error, stdout, stderr }), + ), + ); }; diff --git a/packages/insomnia-inso/src/cli.ts b/packages/insomnia-inso/src/cli.ts index 8186f20902..7db3696c1f 100644 --- a/packages/insomnia-inso/src/cli.ts +++ b/packages/insomnia-inso/src/cli.ts @@ -109,7 +109,7 @@ export class InsoError extends Error { * getAppDataDir returns the data directory for an Electron app, * it is equivalent to the app.getPath('userData') API in Electron. * https://www.electronjs.org/docs/api/app#appgetpathname -*/ + */ export function getAppDataDir(app: string): string { switch (process.platform) { case 'darwin': @@ -146,7 +146,7 @@ export const logErrorAndExit = (err?: Error) => { }; const noConsoleLog = async (callback: () => Promise): Promise => { const oldConsoleLog = console.log; - console.log = () => { }; + console.log = () => {}; try { return await callback(); } finally { @@ -213,10 +213,10 @@ const logTestResult = (reporter: TestReporter, testResults?: RequestTestResult[] const fallbackReporter = testResults.map(r => `${r.status === 'passed' ? '✅' : '❌'} ${r.testCase}`).join('\n'); const reporterMap = { - dot: testResults.map(r => r.status === 'passed' ? '.' : 'F').join(''), + dot: testResults.map(r => (r.status === 'passed' ? '.' : 'F')).join(''), list: fallbackReporter, min: ' ', - progress: `[${testResults.map(r => r.status === 'passed' ? '-' : 'x').join('')}]`, + progress: `[${testResults.map(r => (r.status === 'passed' ? '-' : 'x')).join('')}]`, spec: fallbackReporter, tap: convertToTAP(testResults), }; @@ -226,7 +226,10 @@ Total tests: ${testResults.length} Passed: ${testResults.filter(r => r.status === 'passed').length} Failed: ${testResults.filter(r => r.status === 'failed').length} -${testResults.filter(r => r.status === 'failed').map(r => r.errorMessage).join('\n')}`; +${testResults + .filter(r => r.status === 'failed') + .map(r => r.errorMessage) + .join('\n')}`; return `${reporterMap[reporter] || fallbackReporter}${summary}`; }; function convertToTAP(testCases: RequestTestResult[]): string { @@ -253,7 +256,9 @@ const readFileFromPathOrUrl = async (pathOrUrl: string) => { return readFile(pathOrUrl, 'utf8'); }; const pathToIterationData = async (pathOrUrl: string, env: string[]): Promise => { - const envAsObject = env.map(envString => Object.fromEntries(new URLSearchParams(envString).entries())).reduce((acc, obj) => ({ ...acc, ...obj }), {}); + const envAsObject = env + .map(envString => Object.fromEntries(new URLSearchParams(envString).entries())) + .reduce((acc, obj) => ({ ...acc, ...obj }), {}); const fileType = pathOrUrl.split('.').pop()?.toLowerCase(); const content = await readFileFromPathOrUrl(pathOrUrl); if (!content) { @@ -267,7 +272,9 @@ const getListFromFileOrUrl = (content: string, fileType?: string): Record data && typeof data === 'object' && !Array.isArray(data) && data !== null); + return jsonDataContent.filter( + data => data && typeof data === 'object' && !Array.isArray(data) && data !== null, + ); } throw new Error('Invalid JSON file uploaded, JSON file must be array of key-value pairs.'); } catch (error) { @@ -275,15 +282,20 @@ const getListFromFileOrUrl = (content: string, fileType?: string): Record row.split(',')); + const csvRows = content + .replace(/\r\n|\r/g, '\n') + .split('\n') + .map(row => row.split(',')); // at least 2 rows required for csv if (csvRows.length > 1) { const csvHeaders = csvRows[0]; const csvContentRows = csvRows.slice(1, csvRows.length); - return csvContentRows.map(contentRow => csvHeaders.reduce((acc: Record, cur, idx) => { - acc[cur] = contentRow[idx] ?? ''; - return acc; - }, {})); + return csvContentRows.map(contentRow => + csvHeaders.reduce((acc: Record, cur, idx) => { + acc[cur] = contentRow[idx] ?? ''; + return acc; + }, {}), + ); } throw new Error('CSV file must contain at least two rows with first row as variable names'); } @@ -306,7 +318,6 @@ const transformIterationDataToEnvironmentList = (list: Record[]) }; export const go = (args?: string[]) => { - const program = new commander.Command(); const version = process.env.VERSION || packageJson.version; @@ -341,7 +352,8 @@ export const go = (args?: string[]) => { program .version(version, '-v, --version') - .description(`A CLI for Insomnia! + .description( + `A CLI for Insomnia! With this tool you can test, lint, and export your Insomnia data. Inso will try to detect your locally installed Insomnia data. You can also point it at a git repository folder, or an Insomnia export file. @@ -355,18 +367,19 @@ export const go = (args?: string[]) => { Inso also supports configuration files, by default it will look for .insorc in the current/provided working directory. $ inso export spec --config /some/path/.insorc -`) +`, + ) .option('-w, --workingDir ', 'set working directory/file: .insomnia folder, *.db.json, export.yaml', '') .option('--verbose', 'show additional logs while running the command', false) .option('--ci', 'run in CI, disables all prompts, defaults to false', false) .option('--config ', 'path to configuration file containing above options (.insorc)', '') .option('--printOptions', 'print the loaded options', false); - const run = program.command('run') - .description('Execution utilities'); + const run = program.command('run').description('Execution utilities'); const defaultReporter: TestReporter = 'spec'; - run.command('test [identifier]') + run + .command('test [identifier]') .description('Run Insomnia unit test suites, identifier can be a test suite id or a API Spec id') .option('-e, --env ', 'environment to use', '') .option('-t, --testNamePattern ', 'run tests that match the regex', '') @@ -376,261 +389,76 @@ export const go = (args?: string[]) => { .option('-k, --disableCertValidation', 'disable certificate validation for requests with SSL', false) .option('--httpsProxy ', 'URL for the proxy server for https requests.', proxySettings.httpsProxy) .option('--httpProxy ', 'URL for the proxy server for http requests.', proxySettings.httpProxy) - .option('--noProxy ', 'Comma separated list of hostnames that do not require a proxy to get reached, even if one is specified.', proxySettings.noProxy) - .action(async (identifier, cmd: { env: string; testNamePattern: string; reporter: TestReporter; bail: boolean; keepFile: boolean; disableCertValidation: boolean; ci: boolean; httpsProxy?: string; httpProxy?: string; noProxy?: string }) => { - const globals: GlobalOptions = program.optsWithGlobals(); - const commandOptions = { ...globals, ...cmd }; - const __configFile = await tryToReadInsoConfigFile(commandOptions.config, commandOptions.workingDir); + .option( + '--noProxy ', + 'Comma separated list of hostnames that do not require a proxy to get reached, even if one is specified.', + proxySettings.noProxy, + ) + .action( + async ( + identifier, + cmd: { + env: string; + testNamePattern: string; + reporter: TestReporter; + bail: boolean; + keepFile: boolean; + disableCertValidation: boolean; + ci: boolean; + httpsProxy?: string; + httpProxy?: string; + noProxy?: string; + }, + ) => { + const globals: GlobalOptions = program.optsWithGlobals(); + const commandOptions = { ...globals, ...cmd }; + const __configFile = await tryToReadInsoConfigFile(commandOptions.config, commandOptions.workingDir); - const options = { - ...__configFile?.options || {}, - ...commandOptions, - }; - logger.level = options.verbose ? LogLevel.Verbose : LogLevel.Info; - options.ci && logger.setReporters([new BasicReporter()]); - options.printOptions && logger.log('Loaded options', options, '\n'); - const useLocalAppData = !options.workingDir && !options.exportFile; - let pathToSearch = ''; - if (useLocalAppData) { - logger.warn('No working directory or export file provided, using local app data directory.'); - pathToSearch = localAppDir; - } else { - pathToSearch = path.resolve(options.workingDir || process.cwd(), options.exportFile || ''); - } - if (options.reporter && !reporterTypes.find(r => r === options.reporter)) { - logger.fatal(`Reporter "${options.reporter}" not unrecognized. Options are [${reporterTypes.join(', ')}].`); - return process.exit(1); - } + const options = { + ...(__configFile?.options || {}), + ...commandOptions, + }; + logger.level = options.verbose ? LogLevel.Verbose : LogLevel.Info; + options.ci && logger.setReporters([new BasicReporter()]); + options.printOptions && logger.log('Loaded options', options, '\n'); + const useLocalAppData = !options.workingDir && !options.exportFile; + let pathToSearch = ''; + if (useLocalAppData) { + logger.warn('No working directory or export file provided, using local app data directory.'); + pathToSearch = localAppDir; + } else { + pathToSearch = path.resolve(options.workingDir || process.cwd(), options.exportFile || ''); + } + if (options.reporter && !reporterTypes.find(r => r === options.reporter)) { + logger.fatal(`Reporter "${options.reporter}" not unrecognized. Options are [${reporterTypes.join(', ')}].`); + return process.exit(1); + } - const db = await loadDb({ - pathToSearch, - filterTypes: [], - }); - - // Find suites - const suites = identifier ? loadTestSuites(db, identifier) : await promptTestSuites(db, !!options.ci); - - if (!suites.length) { - logger.fatal('No test suites found; cannot run tests.', identifier); - return process.exit(1); - } - - // Find environment - const workspaceId = suites[0].parentId; - - const environment = options.env ? loadEnvironment(db, workspaceId, options.env) : await promptEnvironment(db, !!options.ci, workspaceId); - - if (!environment) { - logger.fatal('No environment identified; cannot run tests without a valid environment.'); - return process.exit(1); - } - - const transientVariables = { - ...init(), - _id: uuidv4(), - type: EnvironmentType, - parentId: '', - modified: 0, - created: Date.now(), - name: 'Transient Variables', - data: {}, - }; - - const proxyOptions: { - proxyEnabled: boolean; - httpProxy?: string; - httpsProxy?: string; - noProxy?: string; - } = { - proxyEnabled: Boolean(options.httpProxy || options.httpsProxy), - httpProxy: options.httpProxy, - httpsProxy: options.httpsProxy, - noProxy: options.noProxy, - }; - - try { - const sendRequest = await getSendRequestCallbackMemDb(environment._id, db, transientVariables, { validateSSL: !options.disableCertValidation, ...proxyOptions }); - // Generate test file - const testFileContents = generate(suites.map(suite => ({ - name: suite.name, - suites: [], - tests: db.UnitTest.filter(test => test.parentId === suite._id) - .sort((a, b) => a.metaSortKey - b.metaSortKey) - .map(({ name, code, requestId }) => ({ name, code, defaultRequestId: requestId })), - }))); - - const runTestPromise = runTestsCli(testFileContents, { - reporter: options.reporter, - bail: options.bail, - keepFile: options.keepFile, - sendRequest, - testFilter: options.testNamePattern, + const db = await loadDb({ + pathToSearch, + filterTypes: [], }); - // TODO: is this necessary? - const success = options.verbose ? await runTestPromise : await noConsoleLog(() => runTestPromise); - return process.exit(success ? 0 : 1); - } catch (error) { - logErrorAndExit(error); - } - return process.exit(1); - }); + // Find suites + const suites = identifier ? loadTestSuites(db, identifier) : await promptTestSuites(db, !!options.ci); - run.command('collection [identifier]') - .description('Run Insomnia request collection, identifier can be a workspace id') - .option('-t, --requestNamePattern ', 'run requests that match the regex', '') - .option('-i, --item ', 'request or folder id to run', collect, []) - .option('-e, --env ', 'environment to use', '') - .option('-g, --globals ', 'global environment to use (filepath or id)', '') - .option('--delay-request ', 'milliseconds to delay between requests', '0') - .option('--env-var ', 'override environment variables', collect, []) - .option('-n, --iteration-count ', 'number of times to repeat', '1') - .option('-d, --iteration-data ', 'file path or url (JSON or CSV)', '') - .option('-r, --reporter ', `reporter to use, options are [${reporterTypes.join(', ')}]`, defaultReporter) - .option('-b, --bail', 'abort ("bail") after first non-200 response', false) - .option('--disableCertValidation', 'disable certificate validation for requests with SSL', false) - .option('--httpsProxy ', 'URL for the proxy server for https requests.', proxySettings.httpsProxy) - .option('--httpProxy ', 'URL for the proxy server for http requests.', proxySettings.httpProxy) - .option('--noProxy ', 'Comma separated list of hostnames that do not require a proxy to get reached, even if one is specified.', proxySettings.noProxy) - .action(async (identifier, cmd: { env: string; globals: string; disableCertValidation: boolean; requestNamePattern: string; bail: boolean; item: string[]; delayRequest: string; iterationCount: string; iterationData: string; envVar: string[]; httpsProxy?: string; httpProxy?: string; noProxy?: string }) => { - const globals: { config: string; workingDir: string; exportFile: string; ci: boolean; printOptions: boolean; verbose: boolean } = program.optsWithGlobals(); - - const commandOptions = { ...globals, ...cmd }; - const __configFile = await tryToReadInsoConfigFile(commandOptions.config, commandOptions.workingDir); - - const options = { - reporter: defaultReporter, - ...__configFile?.options || {}, - ...commandOptions, - }; - logger.level = options.verbose ? LogLevel.Verbose : LogLevel.Info; - options.ci && logger.setReporters([new BasicReporter()]); - options.printOptions && logger.log('Loaded options', options, '\n'); - let pathToSearch = ''; - const useLocalAppData = !options.workingDir && !options.exportFile; - if (useLocalAppData) { - logger.warn('No working directory or export file provided, using local app data directory.'); - pathToSearch = localAppDir; - } else { - pathToSearch = path.resolve(options.workingDir || process.cwd(), options.exportFile || ''); - } - - const db = await loadDb({ - pathToSearch, - filterTypes: [], - }); - - const workspace = await getWorkspaceOrFallback(db, identifier, options.ci); - if (!workspace) { - logger.fatal('No workspace found in the provided data store or fallbacks.'); - return process.exit(1); - } - - // Find environment - const workspaceId = workspace._id; - // get global env by id from nedb or gitstore, or first element from file - // smell: mutates db - if (options.globals) { - const isGlobalFile = await isFile(options.globals); - if (!isGlobalFile) { - const globalEnv = db.Environment.find(env => matchIdIsh(env, options.globals) || env.name === options.globals); - if (!globalEnv) { - logger.warn('No global environment found with id or name', options.globals); - return process.exit(1); - } - if (globalEnv) { - // attach this global env to the workspace - db.WorkspaceMeta = [{ activeGlobalEnvironmentId: globalEnv._id, _id: `wrkm_${uuidv4().replace(/-/g, '')}`, type: 'WorkspaceMeta', parentId: workspaceId, name: '' }]; - } + if (!suites.length) { + logger.fatal('No test suites found; cannot run tests.', identifier); + return process.exit(1); } - if (isGlobalFile) { - const globalEnvDb = await insomniaExportAdapter(options.globals, ['Environment']); - logger.trace('--globals is a file path, loading from file, global env selection is not currently supported, taking first element'); - const firstGlobalEnv = globalEnvDb?.Environment?.[0]; - if (!firstGlobalEnv) { - logger.warn('No environments found in the file', options.globals); - return process.exit(1); - } - // mutate db to include the global envs - db.Environment = [...db.Environment, ...globalEnvDb.Environment]; - // attach this global env to the workspace - db.WorkspaceMeta = [{ activeGlobalEnvironmentId: firstGlobalEnv._id, _id: `wrkm_${uuidv4().replace(/-/g, '')}`, type: 'WorkspaceMeta', parentId: workspaceId, name: '' }]; + + // Find environment + const workspaceId = suites[0].parentId; + + const environment = options.env + ? loadEnvironment(db, workspaceId, options.env) + : await promptEnvironment(db, !!options.ci, workspaceId); + + if (!environment) { + logger.fatal('No environment identified; cannot run tests without a valid environment.'); + return process.exit(1); } - } - const environment = options.env ? loadEnvironment(db, workspaceId, options.env) : await promptEnvironment(db, !!options.ci, workspaceId); - if (!environment) { - logger.fatal('No environment identified; cannot run requests without a valid environment.'); - return process.exit(1); - } - let requestsToRun = getRequestsToRunFromListOrWorkspace(db, workspaceId, options.item); - if (options.requestNamePattern) { - requestsToRun = requestsToRun.filter(req => req.name.match(new RegExp(options.requestNamePattern))); - } - if (!requestsToRun.length) { - logger.fatal('No requests identified; nothing to run.'); - return process.exit(1); - } - - // sort requests - const isRunningFolder = options.item.length === 1 && options.item[0].startsWith('fld_'); - if (options.item.length && !isRunningFolder) { - const requestOrder = new Map(); - options.item.forEach((reqId: string, order: number) => requestOrder.set(reqId, order + 1)); - requestsToRun = requestsToRun.sort((a, b) => (requestOrder.get(a._id) || requestsToRun.length) - (requestOrder.get(b._id) || requestsToRun.length)); - } else { - const getAllParentGroupSortKeys = (doc: BaseModel): number[] => { - const parentFolder = db.RequestGroup.find(rg => rg._id === doc.parentId); - if (parentFolder === undefined) { - return []; - } - return [(parentFolder as RequestGroup).metaSortKey, ...getAllParentGroupSortKeys(parentFolder)]; - }; - - // sort by metaSortKey (manual sorting order) - requestsToRun = requestsToRun.map(request => { - const allParentGroupSortKeys = getAllParentGroupSortKeys(request as BaseModel); - - return { - ancestors: allParentGroupSortKeys.reverse(), - request, - }; - }).sort((a, b) => { - let compareResult = 0; - - let i = 0, j = 0; - for (; i < a.ancestors.length && j < b.ancestors.length; i++, j++) { - const aSortKey = a.ancestors[i]; - const bSortKey = b.ancestors[j]; - if (aSortKey < bSortKey) { - compareResult = -1; - break; - } else if (aSortKey > bSortKey) { - compareResult = 1; - break; - } - } - if (compareResult !== 0) { - return compareResult; - } - - if (a.ancestors.length === b.ancestors.length) { - return a.request.metaSortKey - b.request.metaSortKey; - } - - if (i < a.ancestors.length) { - return a.ancestors[i] - b.request.metaSortKey; - } else if (j < b.ancestors.length) { - return a.request.metaSortKey - b.ancestors[j]; - } - return 0; - }).map(({ request }) => request); - } - - try { - const iterationCount = parseInt(options.iterationCount, 10); - - const iterationData = await pathToIterationData(options.iterationData, options.envVar); const transientVariables = { ...init(), _id: uuidv4(), @@ -654,62 +482,346 @@ export const go = (args?: string[]) => { noProxy: options.noProxy, }; - const sendRequest = await getSendRequestCallbackMemDb(environment._id, db, transientVariables, { validateSSL: !options.disableCertValidation, ...proxyOptions }, iterationData, iterationCount); - let success = true; - for (let i = 0; i < iterationCount; i++) { - let reqIndex = 0; - while (reqIndex < requestsToRun.length) { - const req = requestsToRun[reqIndex]; + try { + const sendRequest = await getSendRequestCallbackMemDb(environment._id, db, transientVariables, { + validateSSL: !options.disableCertValidation, + ...proxyOptions, + }); + // Generate test file + const testFileContents = generate( + suites.map(suite => ({ + name: suite.name, + suites: [], + tests: db.UnitTest.filter(test => test.parentId === suite._id) + .sort((a, b) => a.metaSortKey - b.metaSortKey) + .map(({ name, code, requestId }) => ({ name, code, defaultRequestId: requestId })), + })), + ); - if (options.bail && !success) { - return; + const runTestPromise = runTestsCli(testFileContents, { + reporter: options.reporter, + bail: options.bail, + keepFile: options.keepFile, + sendRequest, + testFilter: options.testNamePattern, + }); + + // TODO: is this necessary? + const success = options.verbose ? await runTestPromise : await noConsoleLog(() => runTestPromise); + return process.exit(success ? 0 : 1); + } catch (error) { + logErrorAndExit(error); + } + return process.exit(1); + }, + ); + + run + .command('collection [identifier]') + .description('Run Insomnia request collection, identifier can be a workspace id') + .option('-t, --requestNamePattern ', 'run requests that match the regex', '') + .option('-i, --item ', 'request or folder id to run', collect, []) + .option('-e, --env ', 'environment to use', '') + .option('-g, --globals ', 'global environment to use (filepath or id)', '') + .option('--delay-request ', 'milliseconds to delay between requests', '0') + .option('--env-var ', 'override environment variables', collect, []) + .option('-n, --iteration-count ', 'number of times to repeat', '1') + .option('-d, --iteration-data ', 'file path or url (JSON or CSV)', '') + .option('-r, --reporter ', `reporter to use, options are [${reporterTypes.join(', ')}]`, defaultReporter) + .option('-b, --bail', 'abort ("bail") after first non-200 response', false) + .option('--disableCertValidation', 'disable certificate validation for requests with SSL', false) + .option('--httpsProxy ', 'URL for the proxy server for https requests.', proxySettings.httpsProxy) + .option('--httpProxy ', 'URL for the proxy server for http requests.', proxySettings.httpProxy) + .option( + '--noProxy ', + 'Comma separated list of hostnames that do not require a proxy to get reached, even if one is specified.', + proxySettings.noProxy, + ) + .action( + async ( + identifier, + cmd: { + env: string; + globals: string; + disableCertValidation: boolean; + requestNamePattern: string; + bail: boolean; + item: string[]; + delayRequest: string; + iterationCount: string; + iterationData: string; + envVar: string[]; + httpsProxy?: string; + httpProxy?: string; + noProxy?: string; + }, + ) => { + const globals: { + config: string; + workingDir: string; + exportFile: string; + ci: boolean; + printOptions: boolean; + verbose: boolean; + } = program.optsWithGlobals(); + + const commandOptions = { ...globals, ...cmd }; + const __configFile = await tryToReadInsoConfigFile(commandOptions.config, commandOptions.workingDir); + + const options = { + reporter: defaultReporter, + ...(__configFile?.options || {}), + ...commandOptions, + }; + logger.level = options.verbose ? LogLevel.Verbose : LogLevel.Info; + options.ci && logger.setReporters([new BasicReporter()]); + options.printOptions && logger.log('Loaded options', options, '\n'); + let pathToSearch = ''; + const useLocalAppData = !options.workingDir && !options.exportFile; + if (useLocalAppData) { + logger.warn('No working directory or export file provided, using local app data directory.'); + pathToSearch = localAppDir; + } else { + pathToSearch = path.resolve(options.workingDir || process.cwd(), options.exportFile || ''); + } + + const db = await loadDb({ + pathToSearch, + filterTypes: [], + }); + + const workspace = await getWorkspaceOrFallback(db, identifier, options.ci); + if (!workspace) { + logger.fatal('No workspace found in the provided data store or fallbacks.'); + return process.exit(1); + } + + // Find environment + const workspaceId = workspace._id; + // get global env by id from nedb or gitstore, or first element from file + // smell: mutates db + if (options.globals) { + const isGlobalFile = await isFile(options.globals); + if (!isGlobalFile) { + const globalEnv = db.Environment.find( + env => matchIdIsh(env, options.globals) || env.name === options.globals, + ); + if (!globalEnv) { + logger.warn('No global environment found with id or name', options.globals); + return process.exit(1); } - logger.log(`Running request: ${req.name} ${req._id}`); - const res = await sendRequest(req._id, i); - if (!res) { - logger.error('Timed out while running script'); - success = false; - continue; - } - - const timelineString = await readFile(res.timelinePath, 'utf8'); - const appendNewLineIfNeeded = (str: string) => str.endsWith('\n') ? str : str + '\n'; - const timeline = deserializeNDJSON(timelineString).map(e => appendNewLineIfNeeded(e.value)).join(''); - logger.trace(timeline); - if (res.testResults?.length) { - console.log(` -Test results:`); - console.log(logTestResult(options.reporter, res.testResults)); - const hasFailedTests = res.testResults.some(t => t.status === 'failed'); - if (hasFailedTests) { - success = false; - } - } - - await new Promise(r => setTimeout(r, parseInt(options.delayRequest, 10))); - - if (res.nextRequestIdOrName) { - const offset = getNextRequestOffset(requestsToRun.slice(reqIndex), res.nextRequestIdOrName); - reqIndex += offset; - if (reqIndex < requestsToRun.length) { - console.log(`The next request has been pointed to "${requestsToRun[reqIndex].name}"`); - } else { - console.log(`No request has been found for "${res.nextRequestIdOrName}", ending the iteration`); - } - } else { - reqIndex++; + if (globalEnv) { + // attach this global env to the workspace + db.WorkspaceMeta = [ + { + activeGlobalEnvironmentId: globalEnv._id, + _id: `wrkm_${uuidv4().replace(/-/g, '')}`, + type: 'WorkspaceMeta', + parentId: workspaceId, + name: '', + }, + ]; } } + if (isGlobalFile) { + const globalEnvDb = await insomniaExportAdapter(options.globals, ['Environment']); + logger.trace( + '--globals is a file path, loading from file, global env selection is not currently supported, taking first element', + ); + const firstGlobalEnv = globalEnvDb?.Environment?.[0]; + if (!firstGlobalEnv) { + logger.warn('No environments found in the file', options.globals); + return process.exit(1); + } + // mutate db to include the global envs + db.Environment = [...db.Environment, ...globalEnvDb.Environment]; + // attach this global env to the workspace + db.WorkspaceMeta = [ + { + activeGlobalEnvironmentId: firstGlobalEnv._id, + _id: `wrkm_${uuidv4().replace(/-/g, '')}`, + type: 'WorkspaceMeta', + parentId: workspaceId, + name: '', + }, + ]; + } + } + const environment = options.env + ? loadEnvironment(db, workspaceId, options.env) + : await promptEnvironment(db, !!options.ci, workspaceId); + if (!environment) { + logger.fatal('No environment identified; cannot run requests without a valid environment.'); + return process.exit(1); } - return process.exit(success ? 0 : 1); - } catch (error) { - logErrorAndExit(error); - } - return process.exit(1); - }); - program.command('lint') - .description('Lint a yaml file in the workingDir or the provided file path (with .spectral.yml) or a spec in an Insomnia database directory') + let requestsToRun = getRequestsToRunFromListOrWorkspace(db, workspaceId, options.item); + if (options.requestNamePattern) { + requestsToRun = requestsToRun.filter(req => req.name.match(new RegExp(options.requestNamePattern))); + } + if (!requestsToRun.length) { + logger.fatal('No requests identified; nothing to run.'); + return process.exit(1); + } + + // sort requests + const isRunningFolder = options.item.length === 1 && options.item[0].startsWith('fld_'); + if (options.item.length && !isRunningFolder) { + const requestOrder = new Map(); + options.item.forEach((reqId: string, order: number) => requestOrder.set(reqId, order + 1)); + requestsToRun = requestsToRun.sort( + (a, b) => + (requestOrder.get(a._id) || requestsToRun.length) - (requestOrder.get(b._id) || requestsToRun.length), + ); + } else { + const getAllParentGroupSortKeys = (doc: BaseModel): number[] => { + const parentFolder = db.RequestGroup.find(rg => rg._id === doc.parentId); + if (parentFolder === undefined) { + return []; + } + return [(parentFolder as RequestGroup).metaSortKey, ...getAllParentGroupSortKeys(parentFolder)]; + }; + + // sort by metaSortKey (manual sorting order) + requestsToRun = requestsToRun + .map(request => { + const allParentGroupSortKeys = getAllParentGroupSortKeys(request as BaseModel); + + return { + ancestors: allParentGroupSortKeys.reverse(), + request, + }; + }) + .sort((a, b) => { + let compareResult = 0; + + let i = 0, + j = 0; + for (; i < a.ancestors.length && j < b.ancestors.length; i++, j++) { + const aSortKey = a.ancestors[i]; + const bSortKey = b.ancestors[j]; + if (aSortKey < bSortKey) { + compareResult = -1; + break; + } else if (aSortKey > bSortKey) { + compareResult = 1; + break; + } + } + if (compareResult !== 0) { + return compareResult; + } + + if (a.ancestors.length === b.ancestors.length) { + return a.request.metaSortKey - b.request.metaSortKey; + } + + if (i < a.ancestors.length) { + return a.ancestors[i] - b.request.metaSortKey; + } else if (j < b.ancestors.length) { + return a.request.metaSortKey - b.ancestors[j]; + } + return 0; + }) + .map(({ request }) => request); + } + + try { + const iterationCount = parseInt(options.iterationCount, 10); + + const iterationData = await pathToIterationData(options.iterationData, options.envVar); + const transientVariables = { + ...init(), + _id: uuidv4(), + type: EnvironmentType, + parentId: '', + modified: 0, + created: Date.now(), + name: 'Transient Variables', + data: {}, + }; + + const proxyOptions: { + proxyEnabled: boolean; + httpProxy?: string; + httpsProxy?: string; + noProxy?: string; + } = { + proxyEnabled: Boolean(options.httpProxy || options.httpsProxy), + httpProxy: options.httpProxy, + httpsProxy: options.httpsProxy, + noProxy: options.noProxy, + }; + + const sendRequest = await getSendRequestCallbackMemDb( + environment._id, + db, + transientVariables, + { validateSSL: !options.disableCertValidation, ...proxyOptions }, + iterationData, + iterationCount, + ); + let success = true; + for (let i = 0; i < iterationCount; i++) { + let reqIndex = 0; + while (reqIndex < requestsToRun.length) { + const req = requestsToRun[reqIndex]; + + if (options.bail && !success) { + return; + } + logger.log(`Running request: ${req.name} ${req._id}`); + const res = await sendRequest(req._id, i); + if (!res) { + logger.error('Timed out while running script'); + success = false; + continue; + } + + const timelineString = await readFile(res.timelinePath, 'utf8'); + const appendNewLineIfNeeded = (str: string) => (str.endsWith('\n') ? str : str + '\n'); + const timeline = deserializeNDJSON(timelineString) + .map(e => appendNewLineIfNeeded(e.value)) + .join(''); + logger.trace(timeline); + if (res.testResults?.length) { + console.log(` +Test results:`); + console.log(logTestResult(options.reporter, res.testResults)); + const hasFailedTests = res.testResults.some(t => t.status === 'failed'); + if (hasFailedTests) { + success = false; + } + } + + await new Promise(r => setTimeout(r, parseInt(options.delayRequest, 10))); + + if (res.nextRequestIdOrName) { + const offset = getNextRequestOffset(requestsToRun.slice(reqIndex), res.nextRequestIdOrName); + reqIndex += offset; + if (reqIndex < requestsToRun.length) { + console.log(`The next request has been pointed to "${requestsToRun[reqIndex].name}"`); + } else { + console.log(`No request has been found for "${res.nextRequestIdOrName}", ending the iteration`); + } + } else { + reqIndex++; + } + } + } + return process.exit(success ? 0 : 1); + } catch (error) { + logErrorAndExit(error); + } + return process.exit(1); + }, + ); + + program + .command('lint') + .description( + 'Lint a yaml file in the workingDir or the provided file path (with .spectral.yml) or a spec in an Insomnia database directory', + ) .command('spec [identifier]') .description('Lint an API Specification, identifier can be an API Spec id or a file path') .action(async identifier => { @@ -717,17 +829,18 @@ Test results:`); const commandOptions = globals; const __configFile = await tryToReadInsoConfigFile(commandOptions.config, commandOptions.workingDir); const options = { - ...__configFile?.options || {}, + ...(__configFile?.options || {}), ...commandOptions, }; logger.level = options.verbose ? LogLevel.Verbose : LogLevel.Info; options.ci && logger.setReporters([new BasicReporter()]); // Assert identifier is a file - const identifierAsAbsPath = identifier && getAbsoluteFilePath({ workingDir: options.workingDir, file: identifier }); + const identifierAsAbsPath = + identifier && getAbsoluteFilePath({ workingDir: options.workingDir, file: identifier }); let isIdentiferAFile = false; try { isIdentiferAFile = identifier && (await fs.promises.stat(identifierAsAbsPath)).isFile(); - } catch (err) { } + } catch (err) {} const pathToSearch = ''; let specContent; let rulesetFileName; @@ -763,7 +876,9 @@ Test results:`); return process.exit(1); }); - program.command('export').description('Export data from insomnia models') + program + .command('export') + .description('Export data from insomnia models') .command('spec [identifier]') .description('Export an API Specification to a file, identifier can be an API Spec id') .option('-o, --output ', 'save the generated config to a file', '') @@ -773,7 +888,7 @@ Test results:`); const commandOptions = { ...globals, ...cmd }; const __configFile = await tryToReadInsoConfigFile(commandOptions.config, commandOptions.workingDir); const options = { - ...__configFile?.options || {}, + ...(__configFile?.options || {}), ...commandOptions, }; options.printOptions && logger.log('Loaded options', options, '\n'); @@ -788,7 +903,8 @@ Test results:`); specContent, skipAnnotations: options.skipAnnotations, }); - const outputPath = options.output && getAbsoluteFilePath({ workingDir: options.workingDir, file: options.output }); + const outputPath = + options.output && getAbsoluteFilePath({ workingDir: options.workingDir, file: options.output }); if (!outputPath) { logger.log(toExport); return process.exit(0); @@ -803,7 +919,8 @@ Test results:`); }); // Add script base command - program.command('script ') + program + .command('script ') .description('Run scripts defined in .insorc') .allowUnknownOption() .action(async (scriptName: string, cmd) => { @@ -812,7 +929,7 @@ Test results:`); const __configFile = await tryToReadInsoConfigFile(commandOptions.config, commandOptions.workingDir); const options = { - ...__configFile?.options || {}, + ...(__configFile?.options || {}), ...commandOptions, }; logger.level = options.verbose ? LogLevel.Verbose : LogLevel.Info; @@ -822,7 +939,10 @@ Test results:`); const scriptTask = __configFile?.scripts?.[scriptName]; if (!scriptTask) { - logger.fatal(`Could not find inso script "${scriptName}" in the config file.`, Object.keys(__configFile?.scripts || {})); + logger.fatal( + `Could not find inso script "${scriptName}" in the config file.`, + Object.keys(__configFile?.scripts || {}), + ); return process.exit(1); } @@ -833,9 +953,7 @@ Test results:`); // Get args after script name const passThroughArgs = program.args.slice(program.args.indexOf(scriptName) + 1); - const scriptArgs: string[] = parseArgsStringToArgv( - `self ${scriptTask} ${passThroughArgs.join(' ')}`, - ); + const scriptArgs: string[] = parseArgsStringToArgv(`self ${scriptTask} ${passThroughArgs.join(' ')}`); logger.debug(`>> ${scriptArgs.slice(1).join(' ')}`); @@ -845,10 +963,7 @@ Test results:`); program.parseAsync(args || process.argv).catch(logErrorAndExit); }; -const getNextRequestOffset = ( - leftRequestsToRun: Request[], - nextRequestIdOrName: string -) => { +const getNextRequestOffset = (leftRequestsToRun: Request[], nextRequestIdOrName: string) => { const idMatchOffset = leftRequestsToRun.findIndex(req => req._id.trim() === nextRequestIdOrName.trim()); if (idMatchOffset >= 0) { return idMatchOffset; diff --git a/packages/insomnia-inso/src/commands/export-specification.test.ts b/packages/insomnia-inso/src/commands/export-specification.test.ts index 9f4f05f84c..a0342eb24c 100644 --- a/packages/insomnia-inso/src/commands/export-specification.test.ts +++ b/packages/insomnia-inso/src/commands/export-specification.test.ts @@ -47,5 +47,4 @@ paths: description: OK `); }); - }); diff --git a/packages/insomnia-inso/src/commands/export-specification.ts b/packages/insomnia-inso/src/commands/export-specification.ts index 30d478a8f9..650f4de04e 100644 --- a/packages/insomnia-inso/src/commands/export-specification.ts +++ b/packages/insomnia-inso/src/commands/export-specification.ts @@ -5,10 +5,7 @@ import YAML from 'yaml'; import { InsoError } from '../cli'; -export async function writeFileWithCliOptions( - outputPath: string, - contents: string, -): Promise { +export async function writeFileWithCliOptions(outputPath: string, contents: string): Promise { try { await mkdir(path.dirname(outputPath), { recursive: true }); await writeFile(outputPath, contents); @@ -19,7 +16,13 @@ export async function writeFileWithCliOptions( } } -export async function exportSpecification({ specContent, skipAnnotations }: { specContent: string; skipAnnotations: boolean }) { +export async function exportSpecification({ + specContent, + skipAnnotations, +}: { + specContent: string; + skipAnnotations: boolean; +}) { if (!skipAnnotations) { return specContent; } diff --git a/packages/insomnia-inso/src/commands/lint-specification.test.ts b/packages/insomnia-inso/src/commands/lint-specification.test.ts index 3c2c52b81e..10205505c1 100644 --- a/packages/insomnia-inso/src/commands/lint-specification.test.ts +++ b/packages/insomnia-inso/src/commands/lint-specification.test.ts @@ -4,7 +4,6 @@ import { describe, expect, it } from 'vitest'; import { lintSpecification } from './lint-specification'; describe('lint specification', () => { - const specContent = `openapi: '3.0.2' info: title: Sample Spec @@ -66,7 +65,8 @@ paths: responses: '200': description: OK -`, rulesetFileName, +`, + rulesetFileName, }); expect(result.isValid).toBe(true); }); diff --git a/packages/insomnia-inso/src/commands/lint-specification.ts b/packages/insomnia-inso/src/commands/lint-specification.ts index 60cc3f8f9c..0a0a484732 100644 --- a/packages/insomnia-inso/src/commands/lint-specification.ts +++ b/packages/insomnia-inso/src/commands/lint-specification.ts @@ -1,6 +1,6 @@ -import type { RulesetDefinition} from '@stoplight/spectral-core'; +import type { RulesetDefinition } from '@stoplight/spectral-core'; import { Spectral } from '@stoplight/spectral-core'; - + const { bundleAndLoadRuleset } = require('@stoplight/spectral-ruleset-bundler/with-loader'); import { oas } from '@stoplight/spectral-rulesets'; import { DiagnosticSeverity } from '@stoplight/types'; @@ -22,7 +22,13 @@ export const getRuleSetFileFromFolderByFilename = async (filePath: string) => { throw new InsoError(`Failed to read "${filePath}"`, error); } }; -export async function lintSpecification({ specContent, rulesetFileName }: { specContent: string; rulesetFileName?: string },) { +export async function lintSpecification({ + specContent, + rulesetFileName, +}: { + specContent: string; + rulesetFileName?: string; +}) { const spectral = new Spectral(); // Use custom ruleset if present let ruleset = oas; @@ -49,7 +55,11 @@ export async function lintSpecification({ specContent, rulesetFileName }: { spec logger.warn(`${results.filter(r => r.severity === DiagnosticSeverity.Warning).length} lint warnings found. \n`); } results.forEach(r => - logger.log(`${r.range.start.line + 1}:${r.range.start.character + 1} - ${DiagnosticSeverity[r.severity]} - ${r.code} - ${r.message} - ${r.path.join('.')}`), + logger.log( + `${r.range.start.line + 1}:${r.range.start.character + 1} - ${DiagnosticSeverity[r.severity]} - ${r.code} - ${ + r.message + } - ${r.path.join('.')}`, + ), ); // Fail if errors present diff --git a/packages/insomnia-inso/src/db/adapters/git-adapter.ts b/packages/insomnia-inso/src/db/adapters/git-adapter.ts index ed6114dec3..20c7d30413 100644 --- a/packages/insomnia-inso/src/db/adapters/git-adapter.ts +++ b/packages/insomnia-inso/src/db/adapters/git-adapter.ts @@ -24,10 +24,7 @@ const gitAdapter: DbAdapter = async (dir, filterTypes) => { const db = emptyDb(); - const readAndInsertDoc = async ( - type: keyof Database, - fileName: string, - ): Promise => { + const readAndInsertDoc = async (type: keyof Database, fileName: string): Promise => { // Get contents of each file in type dir and insert into data let contents = ''; try { @@ -40,7 +37,7 @@ const gitAdapter: DbAdapter = async (dir, filterTypes) => { (db[type] as {}[]).push(obj); }; - const types = filterTypes?.length ? filterTypes : Object.keys(db) as (keyof Database)[]; + const types = filterTypes?.length ? filterTypes : (Object.keys(db) as (keyof Database)[]); await Promise.all( types.map(async t => { // Get all files in type dir @@ -54,9 +51,7 @@ const gitAdapter: DbAdapter = async (dir, filterTypes) => { } return Promise.all( // Insert each file from each type - files.map(file => - readAndInsertDoc(t, path.join(dir, '.insomnia', t, file)), - ), + files.map(file => readAndInsertDoc(t, path.join(dir, '.insomnia', t, file))), ); }), ); diff --git a/packages/insomnia-inso/src/db/adapters/insomnia-adapter.test.ts b/packages/insomnia-inso/src/db/adapters/insomnia-adapter.test.ts index 1b10bc1582..3d0f613918 100644 --- a/packages/insomnia-inso/src/db/adapters/insomnia-adapter.test.ts +++ b/packages/insomnia-inso/src/db/adapters/insomnia-adapter.test.ts @@ -58,15 +58,13 @@ describe('insomniaAdapter()', () => { expect(db?.UnitTest.length).toBe(0); }); - it.each([ - 'malformed.yaml', - 'no-export-format.yaml', - 'v3-export-format.yaml', - 'empty.yaml', - ])('should throw InsoError if malformed yaml content: %s', async (fileName: string) => { - const pathname = path.join(fixturesPath, 'insomnia-v4', fileName); - await expect(insomniaAdapter(pathname)).rejects.toThrowErrorMatchingSnapshot(); - }); + it.each(['malformed.yaml', 'no-export-format.yaml', 'v3-export-format.yaml', 'empty.yaml'])( + 'should throw InsoError if malformed yaml content: %s', + async (fileName: string) => { + const pathname = path.join(fixturesPath, 'insomnia-v4', fileName); + await expect(insomniaAdapter(pathname)).rejects.toThrowErrorMatchingSnapshot(); + }, + ); it('should return null if pathname is invalid', async () => { const pathname = path.join(fixturesPath, 'insomnia-v4', 'insomnia'); diff --git a/packages/insomnia-inso/src/db/adapters/insomnia-adapter.ts b/packages/insomnia-inso/src/db/adapters/insomnia-adapter.ts index 09d2861e8f..37e341f53f 100644 --- a/packages/insomnia-inso/src/db/adapters/insomnia-adapter.ts +++ b/packages/insomnia-inso/src/db/adapters/insomnia-adapter.ts @@ -29,7 +29,8 @@ import type { BaseModel } from '../models/types'; * @see packages/insomnia/src/common/import.js */ -type RawTypeKey = 'api_spec' +type RawTypeKey = + | 'api_spec' | 'environment' | 'request' | 'request_group' @@ -37,7 +38,6 @@ type RawTypeKey = 'api_spec' | 'unit_test_suite' | 'unit_test'; - const rawTypeToParsedTypeMap: Record = { api_spec: 'ApiSpec', environment: 'Environment', @@ -47,7 +47,6 @@ const rawTypeToParsedTypeMap: Record = { unit_test_suite: 'UnitTestSuite', unit_test: 'UnitTest', }; - type ExtraProperties = Record; @@ -79,10 +78,12 @@ const insomniaAdapter: DbAdapter = async (filePath, filterTypes) => { // Now, reading and parsing const content = await fs.promises.readFile(filePath, { encoding: 'utf-8' }); - let parsed: { - __export_format: number; - resources: RawTypeModel[]; - } | undefined; + let parsed: + | { + __export_format: number; + resources: RawTypeModel[]; + } + | undefined; try { parsed = YAML.parse(content); @@ -108,7 +109,9 @@ const insomniaAdapter: DbAdapter = async (filePath, filterTypes) => { } else if (!parsed.__export_format) { throw new InsoError(`Expected an Insomnia v4 export file; unexpected data found in ${fileName}.`); } else if (parsed.__export_format !== 4 && parsed.__export_format !== 5) { - throw new InsoError(`Expected an Insomnia v4 export file; found an Insomnia v${parsed.__export_format} export file in ${fileName}.`); + throw new InsoError( + `Expected an Insomnia v4 export file; found an Insomnia v${parsed.__export_format} export file in ${fileName}.`, + ); } // Transform filter to a set for faster search diff --git a/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts b/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts index 653e481c2d..97f4a164e5 100644 --- a/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts +++ b/packages/insomnia-inso/src/db/adapters/ne-db-adapter.ts @@ -3,7 +3,7 @@ import { stat } from 'node:fs/promises'; import NeDB from '@seald-io/nedb'; import path from 'path'; -import type { Database, DbAdapter} from '../index'; +import type { Database, DbAdapter } from '../index'; import { emptyDb } from '../index'; import type { BaseModel } from '../models/types'; @@ -16,24 +16,25 @@ const neDbAdapter: DbAdapter = async (dir, filterTypes) => { } const db = emptyDb(); - const types = filterTypes?.length ? filterTypes : Object.keys(db) as (keyof Database)[]; - const promises = types.map(t => - new Promise((resolve, reject) => { - const filePath = path.join(dir, `insomnia.${t}.db`); - const collection = new NeDB({ - autoload: true, - filename: filePath, - corruptAlertThreshold: 0.9, - }); - collection.find({}, (err: Error, docs: BaseModel[]) => { - if (err) { - return reject(err); - } + const types = filterTypes?.length ? filterTypes : (Object.keys(db) as (keyof Database)[]); + const promises = types.map( + t => + new Promise((resolve, reject) => { + const filePath = path.join(dir, `insomnia.${t}.db`); + const collection = new NeDB({ + autoload: true, + filename: filePath, + corruptAlertThreshold: 0.9, + }); + collection.find({}, (err: Error, docs: BaseModel[]) => { + if (err) { + return reject(err); + } - (db[t] as {}[]).push(...docs); - resolve(null); - }); - }), + (db[t] as {}[]).push(...docs); + resolve(null); + }); + }), ); await Promise.all(promises); return db; diff --git a/packages/insomnia-inso/src/db/index.ts b/packages/insomnia-inso/src/db/index.ts index 0eaf6e5281..8218066d49 100644 --- a/packages/insomnia-inso/src/db/index.ts +++ b/packages/insomnia-inso/src/db/index.ts @@ -48,10 +48,7 @@ export const emptyDb = (): Database => ({ Settings: [], }); -export type DbAdapter = ( - dir: string, - filterTypes?: (keyof Database)[], -) => Promise; +export type DbAdapter = (dir: string, filterTypes?: (keyof Database)[]) => Promise; interface Options { pathToSearch: string; @@ -65,10 +62,7 @@ export const isFile = async (path: string) => { return false; } }; -export const loadDb = async ({ - pathToSearch, - filterTypes, -}: Options) => { +export const loadDb = async ({ pathToSearch, filterTypes }: Options) => { // if path to file is provided try to it is an insomnia export file const isFilePath = await isFile(pathToSearch); if (isFilePath) { diff --git a/packages/insomnia-inso/src/db/models/api-spec.ts b/packages/insomnia-inso/src/db/models/api-spec.ts index 96de106c74..00d274bd2c 100644 --- a/packages/insomnia-inso/src/db/models/api-spec.ts +++ b/packages/insomnia-inso/src/db/models/api-spec.ts @@ -4,34 +4,17 @@ import { AutoComplete } from 'enquirer'; import { logger } from '../../cli'; import type { Database } from '../index'; import type { ApiSpec } from './types'; -import { - ensureSingleOrNone, - generateIdIsh, - getDbChoice, - matchIdIsh, -} from './util'; +import { ensureSingleOrNone, generateIdIsh, getDbChoice, matchIdIsh } from './util'; const entity = 'api specification'; -export const loadApiSpec = ( - db: Database, - identifier: string, -): ApiSpec | null | undefined => { - logger.trace( - 'Load %s with identifier `%s` from data store', - entity, - identifier, - ); - const items = db.ApiSpec.filter( - spec => matchIdIsh(spec, identifier) || spec.fileName === identifier, - ); +export const loadApiSpec = (db: Database, identifier: string): ApiSpec | null | undefined => { + logger.trace('Load %s with identifier `%s` from data store', entity, identifier); + const items = db.ApiSpec.filter(spec => matchIdIsh(spec, identifier) || spec.fileName === identifier); logger.trace('Found %d.', items.length); return ensureSingleOrNone(items, entity); }; -export const promptApiSpec = async ( - db: Database, - ci: boolean, -): Promise => { +export const promptApiSpec = async (db: Database, ci: boolean): Promise => { if (ci || !db.ApiSpec.length) { return null; } diff --git a/packages/insomnia-inso/src/db/models/environment.ts b/packages/insomnia-inso/src/db/models/environment.ts index 142108a55c..472ea41d21 100644 --- a/packages/insomnia-inso/src/db/models/environment.ts +++ b/packages/insomnia-inso/src/db/models/environment.ts @@ -7,10 +7,7 @@ import type { Environment } from './types'; import { ensureSingle, generateIdIsh, getDbChoice, matchIdIsh } from './util'; const loadBaseEnvironmentForWorkspace = (db: Database, workspaceId: string): Environment => { - logger.trace( - 'Load base environment for the workspace `%s` from data store', - workspaceId, - ); + logger.trace('Load base environment for the workspace `%s` from data store', workspaceId); const items = db.Environment.filter(environment => environment.parentId === workspaceId); logger.trace('Found %d.', items.length); return ensureSingle(items, 'base environment'); @@ -31,10 +28,7 @@ export const loadEnvironment = ( return loadBaseEnvironmentForWorkspace(db, workspaceId); } - logger.trace( - 'Load sub environment with identifier `%s` from data store', - identifier, - ); + logger.trace('Load sub environment with identifier `%s` from data store', identifier); return db.Environment.find(env => matchIdIsh(env, identifier) || env.name === identifier); }; @@ -49,9 +43,7 @@ export const promptEnvironment = async ( // Get the sub environments const baseWorkspaceEnv = loadBaseEnvironmentForWorkspace(db, workspaceId); - const subEnvironments = db.Environment.filter( - subEnv => subEnv.parentId === baseWorkspaceEnv._id, - ); + const subEnvironments = db.Environment.filter(subEnv => subEnv.parentId === baseWorkspaceEnv._id); if (!subEnvironments.length) { logger.trace('No sub environments found, using base environment'); diff --git a/packages/insomnia-inso/src/db/models/types.ts b/packages/insomnia-inso/src/db/models/types.ts index 77d52631d5..300a5f3b35 100644 --- a/packages/insomnia-inso/src/db/models/types.ts +++ b/packages/insomnia-inso/src/db/models/types.ts @@ -2,46 +2,46 @@ import type { Database } from '../index'; export interface BaseModel { - _id: string; - name: string; - type: keyof Database; - parentId: string; + _id: string; + name: string; + type: keyof Database; + parentId: string; } export interface BaseApiSpec { - fileName: string; - contentType: 'json' | 'yaml'; - contents: string; + fileName: string; + contentType: 'json' | 'yaml'; + contents: string; } export type ApiSpec = BaseModel & BaseApiSpec; interface BaseUnitTestSuite { - name: string; - metaSortKey: number; + name: string; + metaSortKey: number; } export type UnitTestSuite = BaseModel & BaseUnitTestSuite; interface BaseUnitTest { - name: string; - code: string; - requestId: string | null; - metaSortKey: number; + name: string; + code: string; + requestId: string | null; + metaSortKey: number; } export type UnitTest = BaseModel & BaseUnitTest; interface BaseEnvironment { - name: string; - metaSortKey: number; + name: string; + metaSortKey: number; } export type Environment = BaseModel & BaseEnvironment; interface BaseWorkspace { - name: string; - description: string; + name: string; + description: string; } export type WorkspaceMeta = BaseModel & { activeGlobalEnvironmentId: string }; @@ -49,11 +49,11 @@ export type WorkspaceMeta = BaseModel & { activeGlobalEnvironmentId: string }; export type Workspace = BaseModel & BaseWorkspace; export type InsomniaRequest = BaseModel & { - name: string; - description: string; - method: string; - url: string; - headers: Record; - body: string; - metaSortKey: number; + name: string; + description: string; + method: string; + url: string; + headers: Record; + body: string; + metaSortKey: number; }; diff --git a/packages/insomnia-inso/src/db/models/unit-test-suite.ts b/packages/insomnia-inso/src/db/models/unit-test-suite.ts index b75dbc4988..329f3f26f2 100644 --- a/packages/insomnia-inso/src/db/models/unit-test-suite.ts +++ b/packages/insomnia-inso/src/db/models/unit-test-suite.ts @@ -5,33 +5,17 @@ import { logger } from '../../cli'; import type { Database } from '../index'; import { loadApiSpec } from './api-spec'; import type { UnitTestSuite } from './types'; -import { - ensureSingleOrNone, - generateIdIsh, - getDbChoice, - matchIdIsh, -} from './util'; +import { ensureSingleOrNone, generateIdIsh, getDbChoice, matchIdIsh } from './util'; import { loadWorkspace } from './workspace'; -export const loadUnitTestSuite = ( - db: Database, - identifier: string, -): UnitTestSuite | null | undefined => { +export const loadUnitTestSuite = (db: Database, identifier: string): UnitTestSuite | null | undefined => { // Identifier is for one specific suite; find it - logger.trace( - 'Load unit test suite with identifier `%s` from data store', - identifier, - ); - const items = db.UnitTestSuite.filter( - suite => matchIdIsh(suite, identifier) || suite.name === identifier, - ); + logger.trace('Load unit test suite with identifier `%s` from data store', identifier); + const items = db.UnitTestSuite.filter(suite => matchIdIsh(suite, identifier) || suite.name === identifier); logger.trace('Found %d.', items.length); return ensureSingleOrNone(items, 'unit test suite'); }; -export const loadTestSuites = ( - db: Database, - identifier: string, -): UnitTestSuite[] => { +export const loadTestSuites = (db: Database, identifier: string): UnitTestSuite[] => { const apiSpec = loadApiSpec(db, identifier); const workspace = loadWorkspace(db, apiSpec?.parentId || identifier); // if identifier is for an apiSpec or a workspace, return all suites for that workspace @@ -42,19 +26,14 @@ export const loadTestSuites = ( const result = loadUnitTestSuite(db, identifier); return result ? [result] : []; }; -export const promptTestSuites = async ( - db: Database, - ci: boolean, -): Promise => { +export const promptTestSuites = async (db: Database, ci: boolean): Promise => { if (ci) { return []; } const choices = db.ApiSpec.map(spec => [ getDbChoice(generateIdIsh(spec), spec.fileName), - ...db.UnitTestSuite.filter( - suite => suite.parentId === spec.parentId, - ).map(suite => + ...db.UnitTestSuite.filter(suite => suite.parentId === spec.parentId).map(suite => getDbChoice(generateIdIsh(suite), suite.name, { indent: 1, }), diff --git a/packages/insomnia-inso/src/db/models/util.ts b/packages/insomnia-inso/src/db/models/util.ts index 981454c906..12e46463f5 100644 --- a/packages/insomnia-inso/src/db/models/util.ts +++ b/packages/insomnia-inso/src/db/models/util.ts @@ -1,11 +1,9 @@ import { InsoError } from '../../cli'; import type { BaseModel } from './types'; -export const matchIdIsh = ({ _id }: BaseModel, identifier: string) => - _id.startsWith(identifier); +export const matchIdIsh = ({ _id }: BaseModel, identifier: string) => _id.startsWith(identifier); -export const generateIdIsh = ({ _id }: BaseModel, length = 10) => - _id.slice(0, length); +export const generateIdIsh = ({ _id }: BaseModel, length = 10) => _id.slice(0, length); function indent(level: number, code: string, tab = ' |'): string { if (!level || level < 0) { @@ -20,9 +18,9 @@ export const getDbChoice = ( idIsh: string, message: string, config: { - indent?: number; - hint?: string; - } = {}, + indent?: number; + hint?: string; + } = {}, ) => ({ name: idIsh, message: indent(config.indent || 0, message), @@ -30,10 +28,7 @@ export const getDbChoice = ( hint: config.hint || `${idIsh}`, }); -export const ensureSingleOrNone = ( - items: T[], - entity: string, -): T | null | undefined => { +export const ensureSingleOrNone = (items: T[], entity: string): T | null | undefined => { if (items.length === 1) { return items[0]; } @@ -42,9 +37,7 @@ export const ensureSingleOrNone = ( return null; } - throw new InsoError( - `Expected single or no ${entity} in the data store, but found multiple (${items.length}).`, - ); + throw new InsoError(`Expected single or no ${entity} in the data store, but found multiple (${items.length}).`); }; export const ensureSingle = (items: T[], entity: string): T => { if (items.length === 1) { @@ -52,12 +45,8 @@ export const ensureSingle = (items: T[], entity: string): T => { } if (items.length === 0) { - throw new InsoError( - `Expected single ${entity} in the data store, but found none.`, - ); + throw new InsoError(`Expected single ${entity} in the data store, but found none.`); } - throw new InsoError( - `Expected single ${entity} in the data store, but found multiple (${items.length}).`, - ); + throw new InsoError(`Expected single ${entity} in the data store, but found multiple (${items.length}).`); }; diff --git a/packages/insomnia-inso/src/db/models/workspace.ts b/packages/insomnia-inso/src/db/models/workspace.ts index 6187827383..936dc70f90 100644 --- a/packages/insomnia-inso/src/db/models/workspace.ts +++ b/packages/insomnia-inso/src/db/models/workspace.ts @@ -7,20 +7,12 @@ import type { Workspace } from './types'; import { ensureSingleOrNone, generateIdIsh, getDbChoice, matchIdIsh } from './util'; const entity = 'workspace'; export const loadWorkspace = (db: Database, identifier: string) => { - logger.trace( - 'Load workspace with identifier `%s` from data store', - identifier, - ); - const items = db.Workspace.filter(workspace => ( - matchIdIsh(workspace, identifier) || workspace.name === identifier - )); + logger.trace('Load workspace with identifier `%s` from data store', identifier); + const items = db.Workspace.filter(workspace => matchIdIsh(workspace, identifier) || workspace.name === identifier); logger.trace('Found %d.', items.length); return ensureSingleOrNone(items, 'workspace'); }; -export const promptWorkspace = async ( - db: Database, - ci: boolean, -): Promise => { +export const promptWorkspace = async (db: Database, ci: boolean): Promise => { if (ci || !db.Workspace.length) { return null; } diff --git a/packages/insomnia-inso/src/examples/after-response-failed-test.yml b/packages/insomnia-inso/src/examples/after-response-failed-test.yml index 5cb76498be..de3fe825f0 100644 --- a/packages/insomnia-inso/src/examples/after-response-failed-test.yml +++ b/packages/insomnia-inso/src/examples/after-response-failed-test.yml @@ -9,7 +9,7 @@ resources: created: 1725888487017 url: localhost:4010/echo name: New Request - description: "" + description: '' method: GET body: {} parameters: [] @@ -45,7 +45,7 @@ resources: modified: 1725888480476 created: 1725888480476 name: after response test - description: "" + description: '' scope: collection _type: workspace - _id: env_af8d61e3630269b8a124b18968608f85797d57fc diff --git a/packages/insomnia-inso/src/examples/after-response.yml b/packages/insomnia-inso/src/examples/after-response.yml index 653226d033..255334189b 100644 --- a/packages/insomnia-inso/src/examples/after-response.yml +++ b/packages/insomnia-inso/src/examples/after-response.yml @@ -9,7 +9,7 @@ resources: created: 1725888487017 url: localhost:4010/echo name: New Request - description: "" + description: '' method: GET body: {} parameters: [] @@ -39,7 +39,7 @@ resources: modified: 1725888480476 created: 1725888480476 name: after response test - description: "" + description: '' scope: collection _type: workspace - _id: env_af8d61e3630269b8a124b18968608f85797d57fc diff --git a/packages/insomnia-inso/src/examples/folder-inheritance-document.yml b/packages/insomnia-inso/src/examples/folder-inheritance-document.yml index 7b3381d5d8..2a788fd24a 100644 --- a/packages/insomnia-inso/src/examples/folder-inheritance-document.yml +++ b/packages/insomnia-inso/src/examples/folder-inheritance-document.yml @@ -9,11 +9,11 @@ resources: created: 1720531712225 url: https://localhost:4011/echo name: New Request - description: "" + description: '' method: GET body: mimeType: text/plain - text: "{{foo}}" + text: '{{foo}}' parameters: [] headers: - name: Content-Type @@ -21,7 +21,7 @@ resources: - name: User-Agent value: insomnia/9.3.3-beta.0 authentication: {} - preRequestScript: "" + preRequestScript: '' metaSortKey: -1720531712226 isPrivate: false pathParameters: [] @@ -37,11 +37,11 @@ resources: modified: 1720533498913 created: 1720531708748 name: My Folder - description: "" + description: '' environment: foo: bar environmentPropertyOrder: - "&": + '&': - foo metaSortKey: -1720531708748 authentication: @@ -54,7 +54,7 @@ resources: - id: pair_74f3158279464c9c9827dd2be66762ca name: customheader value: jack - description: "" + description: '' disabled: false _type: request_group - _id: wrk_b216792ae69e468aa46ddbf7783c7c76 @@ -62,7 +62,7 @@ resources: modified: 1720533288412 created: 1720533288412 name: folder-inheritance-doc.yaml - description: "" + description: '' scope: design _type: workspace - _id: env_9e44ac71d3a51fc6a8951b8968e45c44c5236355 @@ -88,7 +88,7 @@ resources: modified: 1720533288413 created: 1720533288413 fileName: New Document - contents: "" + contents: '' contentType: yaml _type: api_spec - _id: uts_412db4d4943a4baeb1319b3bd37fa4bc diff --git a/packages/insomnia-inso/src/examples/global-environment.yml b/packages/insomnia-inso/src/examples/global-environment.yml index 8cb735ef19..e72731dd49 100644 --- a/packages/insomnia-inso/src/examples/global-environment.yml +++ b/packages/insomnia-inso/src/examples/global-environment.yml @@ -9,7 +9,7 @@ resources: modified: 1728298565335 created: 1728298565335 name: GlobalEnv - description: "" + description: '' scope: environment _type: workspace - _id: env_dcf0363e684fae59a4ac57c09c208674caa8a64a @@ -25,7 +25,7 @@ resources: firstkey: first secondkey: second dataPropertyOrder: - "&": + '&': - apiKey - newkey - base_path diff --git a/packages/insomnia-inso/src/examples/minimal.yml b/packages/insomnia-inso/src/examples/minimal.yml index 6cc6bf5fc8..e4bb33b184 100644 --- a/packages/insomnia-inso/src/examples/minimal.yml +++ b/packages/insomnia-inso/src/examples/minimal.yml @@ -9,7 +9,7 @@ resources: created: 1636141100570 url: http://127.0.0.1:4010/ name: with pre request script - description: "" + description: '' method: GET body: {} parameters: [] @@ -28,7 +28,7 @@ resources: modified: 1636140994423 created: 1636140994423 name: Minimal Collection - description: "" + description: '' scope: collection _type: workspace - _id: env_9e44ac71d3a51fc6a8951b8968e45c44c5236355 diff --git a/packages/insomnia-inso/src/examples/script-add-header.yml b/packages/insomnia-inso/src/examples/script-add-header.yml index 828cc3e162..07f67adbc7 100644 --- a/packages/insomnia-inso/src/examples/script-add-header.yml +++ b/packages/insomnia-inso/src/examples/script-add-header.yml @@ -9,7 +9,7 @@ resources: created: 1636141100570 url: http://127.0.0.1:4010 name: custom header by pre request script - description: "" + description: '' method: GET body: {} parameters: [] @@ -28,7 +28,7 @@ resources: modified: 1636140994423 created: 1636140994423 name: Minimal Collection 2 - description: "" + description: '' scope: collection _type: workspace - _id: env_9e44ac71d3a51fc6a8951b8968e45c44c5236355 diff --git a/packages/insomnia-inso/src/examples/script-require.yml b/packages/insomnia-inso/src/examples/script-require.yml index eb026f7e8a..231c1480c1 100644 --- a/packages/insomnia-inso/src/examples/script-require.yml +++ b/packages/insomnia-inso/src/examples/script-require.yml @@ -9,7 +9,7 @@ resources: created: 1636141014552 url: http://127.0.0.1:4010/echo name: insomnia.request manipulation - description: "" + description: '' method: POST parameters: [] headers: @@ -41,7 +41,7 @@ resources: raw: 'rawContent', }); body: - mimeType: "application/json" + mimeType: 'application/json' text: |- {} _type: request @@ -50,7 +50,7 @@ resources: modified: 1636140994423 created: 1636140994423 name: Minimal Collection 3 - description: "" + description: '' scope: collection _type: workspace - _id: env_9e44ac71d3a51fc6a8951b8968e45c44c5236355 diff --git a/packages/insomnia-inso/src/examples/script-send-request.yml b/packages/insomnia-inso/src/examples/script-send-request.yml index f395c6f54b..8b9b9b93bf 100644 --- a/packages/insomnia-inso/src/examples/script-send-request.yml +++ b/packages/insomnia-inso/src/examples/script-send-request.yml @@ -9,7 +9,7 @@ resources: created: 1722852503814 url: localhost:4010/echo name: sendRequest - description: "" + description: '' method: GET body: {} parameters: [] @@ -18,7 +18,7 @@ resources: value: insomnia/9.3.3 authentication: {} preRequestScript: |- - + const resp = await new Promise((resolve, reject) => { insomnia.sendRequest( 'http://localhost:4010/echo', @@ -44,7 +44,7 @@ resources: modified: 1722852498102 created: 1722852498102 name: send request script - description: "" + description: '' scope: collection _type: workspace - _id: env_fa53fdd70393dd67a77da3c45c26f2b75ecbc1f2 diff --git a/packages/insomnia-inso/src/examples/set-next-request.yml b/packages/insomnia-inso/src/examples/set-next-request.yml index 06d8275770..aee70ea5a3 100644 --- a/packages/insomnia-inso/src/examples/set-next-request.yml +++ b/packages/insomnia-inso/src/examples/set-next-request.yml @@ -9,7 +9,7 @@ resources: created: 1729981763331 url: localhost:4010/echo name: setNextRequest - description: "" + description: '' method: GET body: {} parameters: [] @@ -34,7 +34,7 @@ resources: modified: 1729981758905 created: 1729981758905 name: test next request - description: "" + description: '' scope: collection _type: workspace - _id: req_6a0343d51ca74de7a2c73e34211354ab @@ -43,7 +43,7 @@ resources: created: 1729981846012 url: localhost:4010/echo name: Failing Request - description: "" + description: '' method: GET body: {} parameters: [] @@ -71,7 +71,7 @@ resources: created: 1729981822432 url: localhost:4010/echo name: Passing Request - description: "" + description: '' method: GET body: {} parameters: [] diff --git a/packages/insomnia-inso/src/examples/three-requests.yml b/packages/insomnia-inso/src/examples/three-requests.yml index 2a14ed8933..71e80be932 100644 --- a/packages/insomnia-inso/src/examples/three-requests.yml +++ b/packages/insomnia-inso/src/examples/three-requests.yml @@ -8,8 +8,8 @@ resources: modified: 1726666263873 created: 1726658232232 url: localhost:4010/echo - name: "1" - description: "" + name: '1' + description: '' method: GET body: mimeType: text/plain @@ -45,7 +45,7 @@ resources: modified: 1726658198059 created: 1726658198059 name: Collection with 3 requests - description: "" + description: '' scope: collection _type: workspace - _id: req_6063adcdab5b409e9b4f00f47322df4a @@ -53,8 +53,8 @@ resources: modified: 1726658253319 created: 1726658253319 url: localhost:4010/echo - name: "2" - description: "" + name: '2' + description: '' method: GET body: {} parameters: [] @@ -77,8 +77,8 @@ resources: modified: 1726658259060 created: 1726658259060 url: localhost:4010/echo - name: "3" - description: "" + name: '3' + description: '' method: GET body: {} parameters: [] @@ -104,7 +104,7 @@ resources: data: value: 123 dataPropertyOrder: - "&": + '&': - value color: null isPrivate: false diff --git a/packages/insomnia-inso/src/examples/with-missing-env-vars.yml b/packages/insomnia-inso/src/examples/with-missing-env-vars.yml index 8813f86d03..d0f287e36b 100644 --- a/packages/insomnia-inso/src/examples/with-missing-env-vars.yml +++ b/packages/insomnia-inso/src/examples/with-missing-env-vars.yml @@ -8,8 +8,8 @@ resources: modified: 1726666263873 created: 1726658232232 url: localhost:4010/echo - name: "1" - description: "" + name: '1' + description: '' method: GET body: mimeType: text/plain @@ -45,7 +45,7 @@ resources: modified: 1726658198059 created: 1726658198059 name: Collection with 3 requests - description: "" + description: '' scope: collection _type: workspace - _id: env_86e1354fb9909cdb109ccadf83c3353f3bb9bd09 @@ -55,7 +55,7 @@ resources: name: Base Environment data: {} dataPropertyOrder: - "&": + '&': - value color: null isPrivate: false diff --git a/packages/insomnia-inso/src/get-options.test.ts b/packages/insomnia-inso/src/get-options.test.ts index 9bb9378cf5..be47264f0c 100644 --- a/packages/insomnia-inso/src/get-options.test.ts +++ b/packages/insomnia-inso/src/get-options.test.ts @@ -11,8 +11,7 @@ describe('tryToReadInsoConfigFile()', () => { it('should load .insorc-test.yaml config file in fixtures dir', async () => { const result = await tryToReadInsoConfigFile(path.join(fixturesDir, '.insorc-test.yaml')); expect(result).toEqual({ - options: { - }, + options: {}, scripts: { exportSpec: 'inso export spec', lintSpec: 'inso lint spec', @@ -23,8 +22,8 @@ describe('tryToReadInsoConfigFile()', () => { }); it('should return empty object and report error if specified config file not found', async () => { - const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { }); - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { }); + const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); const result = await tryToReadInsoConfigFile('not-found.yaml'); expect(result).toEqual({}); expect(consoleLogSpy).toHaveBeenCalledWith('Could not find config file at not-found.yaml.'); diff --git a/packages/insomnia-inso/src/scripts/artifacts.ts b/packages/insomnia-inso/src/scripts/artifacts.ts index 84bb4e6c9f..40b665895f 100644 --- a/packages/insomnia-inso/src/scripts/artifacts.ts +++ b/packages/insomnia-inso/src/scripts/artifacts.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises'; import process from 'node:process'; -import type { ProcessEnvOptions} from 'child_process'; +import type { ProcessEnvOptions } from 'child_process'; import { spawn } from 'child_process'; import path from 'path'; @@ -14,24 +14,21 @@ const spawnCompressProcess = (cwd: ProcessEnvOptions['cwd']) => { const version = process.env.VERSION || packageJson.version; if (platform === 'darwin') { - return spawn('ditto', [ - '-c', - '-k', - '../binaries/inso', - `inso-macos-${version}.zip`, - ], { cwd }); + return spawn('ditto', ['-c', '-k', '../binaries/inso', `inso-macos-${version}.zip`], { cwd }); } if (platform === 'win32' || platform === 'linux') { - return spawn('tar', [ - '-C', - '../binaries', - platform === 'win32' ? '-a -cf' : '-cJf', - platform === 'win32' - ? `inso-windows-${version}.zip` - : `inso-linux-${process.arch}-${version}.tar.xz`, - platform === 'win32' ? 'inso.exe' : 'inso', - ], { cwd, shell: platform === 'win32' }); + return spawn( + 'tar', + [ + '-C', + '../binaries', + platform === 'win32' ? '-a -cf' : '-cJf', + platform === 'win32' ? `inso-windows-${version}.zip` : `inso-linux-${process.arch}-${version}.tar.xz`, + platform === 'win32' ? 'inso.exe' : 'inso', + ], + { cwd, shell: platform === 'win32' }, + ); } throw new Error(`[pkg-inso-artifacts] Unsupported OS: ${platform}`); diff --git a/packages/insomnia-inso/tsconfig.json b/packages/insomnia-inso/tsconfig.json index 7d9cf547c1..fecf77eac2 100644 --- a/packages/insomnia-inso/tsconfig.json +++ b/packages/insomnia-inso/tsconfig.json @@ -10,9 +10,7 @@ "moduleResolution": "node", "isolatedModules": true, "paths": { - "electron": [ - "../insomnia/send-request/electron", - ], + "electron": ["../insomnia/send-request/electron"] }, /* remove this once react AlertModal is out of the plugins code path */ "jsx": "react", @@ -20,27 +18,16 @@ "module": "CommonJS", "sourceMap": true, /* Runs in the DOM NOTE: this is inconsistent with reality */ - "lib": [ - "ES2023", - "DOM", - "DOM.Iterable" - ], + "lib": ["ES2023", "DOM", "DOM.Iterable"], /* Strictness */ "strict": true, "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "useUnknownInCatchVariables": false, + "useUnknownInCatchVariables": false }, - "include": [ - ".eslintrc.js", - "esbuild.ts", - "jest.config.js", - "package.json", - "src", - "../insomnia/types", - ], + "include": [".eslintrc.js", "esbuild.ts", "jest.config.js", "package.json", "src", "../insomnia/types"], "exclude": [ "**/*.test.ts", "**/__mocks__/*", @@ -51,6 +38,6 @@ "jest.config.js", "node_modules", "scripts", - "src/vitest", - ], + "src/vitest" + ] } diff --git a/packages/insomnia-inso/vitest.config.ts b/packages/insomnia-inso/vitest.config.ts index ebbb8cad3b..fcd4fe5f40 100644 --- a/packages/insomnia-inso/vitest.config.ts +++ b/packages/insomnia-inso/vitest.config.ts @@ -8,9 +8,7 @@ export default defineConfig({ }, server: { deps: { - inline: [ - 'tinykeys', - ], + inline: ['tinykeys'], }, }, }, diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/auth.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/auth.test.ts index b13faaa959..29b94232da 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/auth.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/auth.test.ts @@ -4,191 +4,185 @@ import { type AuthOptions, fromPreRequestAuth, RequestAuth, toPreRequestAuth } f import type { Variable, VariableList } from '../variables'; const varListToObject = (obj: VariableList | undefined) => { - if (!obj) { - return undefined; - } + if (!obj) { + return undefined; + } - return obj.map( - (optVar: Variable) => ({ - // type: 'any', // TODO: fix type - key: optVar.key, - value: optVar.value, - }), - {} - ); + return obj.map( + (optVar: Variable) => ({ + // type: 'any', // TODO: fix type + key: optVar.key, + value: optVar.value, + }), + {}, + ); }; describe('test sdk objects', () => { - it('test RequestAuth methods', () => { - expect(RequestAuth.isValidType('noauth')).toBeTruthy(); + it('test RequestAuth methods', () => { + expect(RequestAuth.isValidType('noauth')).toBeTruthy(); - const basicAuthOptions = { - type: 'basic', - basic: [ - { key: 'username', value: 'user1' }, - { key: 'password', value: 'pwd1' }, - ], - } as AuthOptions; + const basicAuthOptions = { + type: 'basic', + basic: [ + { key: 'username', value: 'user1' }, + { key: 'password', value: 'pwd1' }, + ], + } as AuthOptions; - const authObj = new RequestAuth(basicAuthOptions); + const authObj = new RequestAuth(basicAuthOptions); - const basicAuthOptsFromAuth = varListToObject(authObj.parameters()); - expect(basicAuthOptsFromAuth).toEqual(basicAuthOptions.basic); + const basicAuthOptsFromAuth = varListToObject(authObj.parameters()); + expect(basicAuthOptsFromAuth).toEqual(basicAuthOptions.basic); - const basicAuthOptions2 = { - type: 'basic', - basic: [ - { key: 'username', value: 'user2' }, - { key: 'password', value: 'pwd2' }, - ], - } as AuthOptions; - const bearerAuthOptions = { - type: 'bearer', - bearer: [ - { key: 'token', value: 'mytoken' }, - ], - } as AuthOptions; + const basicAuthOptions2 = { + type: 'basic', + basic: [ + { key: 'username', value: 'user2' }, + { key: 'password', value: 'pwd2' }, + ], + } as AuthOptions; + const bearerAuthOptions = { + type: 'bearer', + bearer: [{ key: 'token', value: 'mytoken' }], + } as AuthOptions; - authObj.update(basicAuthOptions2); - const basicAuthOpt2FromAuth = varListToObject(authObj.parameters()); - expect(basicAuthOpt2FromAuth).toEqual(basicAuthOptions2.basic); + authObj.update(basicAuthOptions2); + const basicAuthOpt2FromAuth = varListToObject(authObj.parameters()); + expect(basicAuthOpt2FromAuth).toEqual(basicAuthOptions2.basic); - authObj.use('bearer', bearerAuthOptions); - const beareerAuthOptFromAuth = varListToObject(authObj.parameters()); - expect(beareerAuthOptFromAuth).toEqual(bearerAuthOptions.bearer); + authObj.use('bearer', bearerAuthOptions); + const beareerAuthOptFromAuth = varListToObject(authObj.parameters()); + expect(beareerAuthOptFromAuth).toEqual(bearerAuthOptions.bearer); - authObj.clear('bearer'); - expect(authObj.parameters()).toBeUndefined(); - }); + authObj.clear('bearer'); + expect(authObj.parameters()).toBeUndefined(); + }); }); describe('test auth transforming', () => { - it('transforming from script side to Insomnia and the reverse direction', () => { - const basicAuth = { - type: 'basic', - useISO88591: true, - disabled: false, - username: 'uname', - password: 'pwd', - }; - const apikeyAuth = { - type: 'apikey', - disabled: false, - key: 'key', - value: 'value', - addTo: 'addto', - }; - const hawkAuth = { - type: 'hawk', - disabled: true, - algorithm: 'sha256', - id: 'id', - key: 'key', - ext: 'ext', - validatePayload: true, - }; - const oauth1Auth = { - type: 'oauth1', - disabled: true, - signatureMethod: 'HMAC-SHA1', - consumerKey: 'consumerKey', - consumerSecret: 'consumerSecret', - tokenKey: 'tokenKey', - tokenSecret: 'tokenSecret', - privateKey: 'privateKey', - version: 'version', - nonce: 'nonce', - timestamp: 'timestamp', - callback: 'callback', - realm: 'realm', - verifier: 'verifier', - includeBodyHash: true, - }; - const digestAuth = { - type: 'digest', - disabled: true, - username: 'username', - password: 'password', - }; - const digestNtlm = { - type: 'ntlm', - disabled: true, - username: 'username', - password: 'password', - }; - const bearerAuth = { - type: 'bearer', - disabled: true, - token: 'token', - prefix: 'prefix', - }; - const awsv4Auth = { - type: 'iam', - disabled: true, - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - region: 'region', - service: 'service', - }; - const asapAuth = { - type: 'asap', - disabled: true, - issuer: 'issuer', - subject: 'subject', - audience: 'audience', - additionalClaims: 'additionalClaims', - keyId: 'keyId', - privateKey: 'privateKey', - }; - const noneAuth = { - type: 'none', - disabled: true, - }; - const oauth2Auth = { - type: 'oauth2', - disabled: true, - grantType: 'authorization_code', - accessTokenUrl: 'accessTokenUrl', - authorizationUrl: 'authorizationUrl', - clientId: 'clientId', - clientSecret: 'clientSecret', - audience: 'audience', - scope: 'scope', - resource: 'resource', - username: 'username', - password: 'password', - redirectUrl: 'redirectUrl', - credentialsInBody: true, - state: 'state', - code: 'code', - accessToken: 'accessToken', - refreshToken: 'refreshToken', - tokenPrefix: 'tokenPrefix', - usePkce: true, - pkceMethod: 'pkceMethod', - responseType: 'id_token', - origin: 'origin', - }; + it('transforming from script side to Insomnia and the reverse direction', () => { + const basicAuth = { + type: 'basic', + useISO88591: true, + disabled: false, + username: 'uname', + password: 'pwd', + }; + const apikeyAuth = { + type: 'apikey', + disabled: false, + key: 'key', + value: 'value', + addTo: 'addto', + }; + const hawkAuth = { + type: 'hawk', + disabled: true, + algorithm: 'sha256', + id: 'id', + key: 'key', + ext: 'ext', + validatePayload: true, + }; + const oauth1Auth = { + type: 'oauth1', + disabled: true, + signatureMethod: 'HMAC-SHA1', + consumerKey: 'consumerKey', + consumerSecret: 'consumerSecret', + tokenKey: 'tokenKey', + tokenSecret: 'tokenSecret', + privateKey: 'privateKey', + version: 'version', + nonce: 'nonce', + timestamp: 'timestamp', + callback: 'callback', + realm: 'realm', + verifier: 'verifier', + includeBodyHash: true, + }; + const digestAuth = { + type: 'digest', + disabled: true, + username: 'username', + password: 'password', + }; + const digestNtlm = { + type: 'ntlm', + disabled: true, + username: 'username', + password: 'password', + }; + const bearerAuth = { + type: 'bearer', + disabled: true, + token: 'token', + prefix: 'prefix', + }; + const awsv4Auth = { + type: 'iam', + disabled: true, + accessKeyId: 'accessKeyId', + secretAccessKey: 'secretAccessKey', + sessionToken: 'sessionToken', + region: 'region', + service: 'service', + }; + const asapAuth = { + type: 'asap', + disabled: true, + issuer: 'issuer', + subject: 'subject', + audience: 'audience', + additionalClaims: 'additionalClaims', + keyId: 'keyId', + privateKey: 'privateKey', + }; + const noneAuth = { + type: 'none', + disabled: true, + }; + const oauth2Auth = { + type: 'oauth2', + disabled: true, + grantType: 'authorization_code', + accessTokenUrl: 'accessTokenUrl', + authorizationUrl: 'authorizationUrl', + clientId: 'clientId', + clientSecret: 'clientSecret', + audience: 'audience', + scope: 'scope', + resource: 'resource', + username: 'username', + password: 'password', + redirectUrl: 'redirectUrl', + credentialsInBody: true, + state: 'state', + code: 'code', + accessToken: 'accessToken', + refreshToken: 'refreshToken', + tokenPrefix: 'tokenPrefix', + usePkce: true, + pkceMethod: 'pkceMethod', + responseType: 'id_token', + origin: 'origin', + }; - [ - basicAuth, - apikeyAuth, - hawkAuth, - oauth1Auth, - digestAuth, - digestNtlm, - bearerAuth, - awsv4Auth, - asapAuth, - noneAuth, - oauth2Auth, - ].forEach(authMethod => { - expect(fromPreRequestAuth( - new RequestAuth( - toPreRequestAuth(authMethod)), - ) - ).toEqual(authMethod); - }); + [ + basicAuth, + apikeyAuth, + hawkAuth, + oauth1Auth, + digestAuth, + digestNtlm, + bearerAuth, + awsv4Auth, + asapAuth, + noneAuth, + oauth2Auth, + ].forEach(authMethod => { + expect(fromPreRequestAuth(new RequestAuth(toPreRequestAuth(authMethod)))).toEqual(authMethod); }); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/certificates.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/certificates.test.ts index a58b3d2e94..490237b651 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/certificates.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/certificates.test.ts @@ -3,33 +3,30 @@ import { describe, expect, it } from 'vitest'; import { Certificate } from '../certificates'; describe('test Certificate object', () => { - it('test methods', () => { - const cert = new Certificate({ - name: 'Certificate for example.com', - matches: ['https://example.com'], - key: { src: '/User/path/to/certificate/key' }, - cert: { src: '/User/path/to/certificate' }, - passphrase: 'iampassphrase', - }); - - [ - 'https://example.com', - 'https://example.com/subdomain', - ].forEach(testCase => { - expect(cert.canApplyTo(testCase)).toBeTruthy(); - }); - - cert.update({ - name: 'Certificate for api.com', - matches: ['https://api.com'], - key: { src: '/User/path/to/certificate/key' }, - cert: { src: '/User/path/to/certificate' }, - passphrase: 'iampassphrase', - }); - - expect(cert.name).toEqual('Certificate for api.com'); - expect(cert.key).toEqual({ src: '/User/path/to/certificate/key' }); - expect(cert.cert).toEqual({ src: '/User/path/to/certificate' }); - expect(cert.passphrase).toEqual('iampassphrase'); + it('test methods', () => { + const cert = new Certificate({ + name: 'Certificate for example.com', + matches: ['https://example.com'], + key: { src: '/User/path/to/certificate/key' }, + cert: { src: '/User/path/to/certificate' }, + passphrase: 'iampassphrase', }); + + ['https://example.com', 'https://example.com/subdomain'].forEach(testCase => { + expect(cert.canApplyTo(testCase)).toBeTruthy(); + }); + + cert.update({ + name: 'Certificate for api.com', + matches: ['https://api.com'], + key: { src: '/User/path/to/certificate/key' }, + cert: { src: '/User/path/to/certificate' }, + passphrase: 'iampassphrase', + }); + + expect(cert.name).toEqual('Certificate for api.com'); + expect(cert.key).toEqual({ src: '/User/path/to/certificate/key' }); + expect(cert.cert).toEqual({ src: '/User/path/to/certificate' }); + expect(cert.passphrase).toEqual('iampassphrase'); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/cookies.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/cookies.test.ts index 7580bb177d..b4dbed3c4b 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/cookies.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/cookies.test.ts @@ -3,227 +3,201 @@ import { describe, expect, it } from 'vitest'; import { Cookie, CookieJar, CookieList, CookieObject, mergeCookieJar } from '../cookies'; describe('test Cookie object', () => { - it('test basic operations', () => { - const cookieStr1 = 'key=value; Domain=inso.com; Path=/; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Max-Age=0;Secure;HttpOnly;HostOnly;Session'; + it('test basic operations', () => { + const cookieStr1 = + 'key=value; Domain=inso.com; Path=/; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Max-Age=0;Secure;HttpOnly;HostOnly;Session'; - expect( - Cookie.parse(cookieStr1) - ).toEqual({ - key: 'key', - value: 'value', - domain: 'inso.com', - expires: new Date('2015-10-21T07:28:00.000Z'), - maxAge: 0, - path: '/', - secure: true, - httpOnly: true, - hostOnly: true, - session: true, - extensions: [], - }); + expect(Cookie.parse(cookieStr1)).toEqual({ + key: 'key', + value: 'value', + domain: 'inso.com', + expires: new Date('2015-10-21T07:28:00.000Z'), + maxAge: 0, + path: '/', + secure: true, + httpOnly: true, + hostOnly: true, + session: true, + extensions: [], + }); - const cookie1Opt = { - key: 'myCookie', - value: 'myCookie', - expires: '01 Jan 1970 00:00:01 GMT', - maxAge: 7, - domain: 'domain.com', - path: '/', - secure: true, - httpOnly: true, - hostOnly: true, - session: true, - extensions: [{ key: 'Ext', value: 'ExtValue' }], - }; - const cookie1 = new Cookie(cookie1Opt); + const cookie1Opt = { + key: 'myCookie', + value: 'myCookie', + expires: '01 Jan 1970 00:00:01 GMT', + maxAge: 7, + domain: 'domain.com', + path: '/', + secure: true, + httpOnly: true, + hostOnly: true, + session: true, + extensions: [{ key: 'Ext', value: 'ExtValue' }], + }; + const cookie1 = new Cookie(cookie1Opt); - const expectedCookieString = 'myCookie=myCookie; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=7; Path=/; Secure; HttpOnly; HostOnly; Ext=ExtValue'; + const expectedCookieString = + 'myCookie=myCookie; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=7; Path=/; Secure; HttpOnly; HostOnly; Ext=ExtValue'; - expect(cookie1.toString()).toEqual(expectedCookieString); - expect(Cookie.stringify(cookie1)).toEqual(expectedCookieString); + expect(cookie1.toString()).toEqual(expectedCookieString); + expect(Cookie.stringify(cookie1)).toEqual(expectedCookieString); - const cookie2 = new Cookie(expectedCookieString); - expect(cookie2.toString()).toEqual(expectedCookieString); - expect(Cookie.stringify(cookie2)).toEqual(expectedCookieString); + const cookie2 = new Cookie(expectedCookieString); + expect(cookie2.toString()).toEqual(expectedCookieString); + expect(Cookie.stringify(cookie2)).toEqual(expectedCookieString); - const c1 = new Cookie({ + const c1 = new Cookie({ + key: 'c1', + value: 'c1', + maxAge: 1, + }); + const c2 = new Cookie({ + key: 'c2', + value: 'c2', + maxAge: 2, + }); + const CookieListStr = Cookie.unparse([c1, c2]); + expect(CookieListStr).toEqual('c1=c1; Max-Age=1; c2=c2; Max-Age=2'); + + expect(Cookie.unparseSingle(cookie1Opt)).toEqual(expectedCookieString); + + const malformedCookieString = '=gingerale'; + expect(Cookie.parse(malformedCookieString)).toEqual({ + key: '', + value: 'gingerale', + expires: 'Infinity', + hostOnly: false, + httpOnly: false, + maxAge: null, + secure: false, + session: false, + }); + }); + + it('test cookie transforming', () => { + const fakeBaseModel = { + _id: '', + type: '', + parentId: '', + modified: 0, + created: 0, + isPrivate: false, + name: '', + }; + const cookieJars = [ + { + cookies: [ + { + id: '1', key: 'c1', - value: 'c1', - maxAge: 1, - }); - const c2 = new Cookie({ - key: 'c2', - value: 'c2', - maxAge: 2, - }); - const CookieListStr = Cookie.unparse([c1, c2]); - expect(CookieListStr).toEqual( - 'c1=c1; Max-Age=1; c2=c2; Max-Age=2' - ); - - expect( - Cookie.unparseSingle(cookie1Opt) - ).toEqual(expectedCookieString); - - const malformedCookieString = '=gingerale'; - expect( - Cookie.parse(malformedCookieString) - ).toEqual({ - key: '', - value: 'gingerale', + value: 'v1', expires: 'Infinity', - hostOnly: false, - httpOnly: false, - maxAge: null, - secure: false, - session: false, - }); - }); - - it('test cookie transforming', () => { - const fakeBaseModel = { - _id: '', - type: '', - parentId: '', - modified: 0, - created: 0, - isPrivate: false, - name: '', - }; - const cookieJars = [ - { - cookies: [ - { - id: '1', - key: 'c1', - value: 'v1', - expires: 'Infinity', - domain: 'inso.com', - path: '/', - secure: true, - httpOnly: true, - extensions: [], - creation: new Date(), - creationIndex: 0, - hostOnly: true, - pathIsDefault: true, - lastAccessed: new Date(), - }, - { - id: '2', - key: 'c2', - value: 'v2', - expires: new Date('08 Aug 1988 08:08:08 GMT'), - domain: 'inso.com', - path: '/', - secure: true, - httpOnly: true, - extensions: [], - creation: new Date(), - creationIndex: 0, - hostOnly: true, - pathIsDefault: true, - lastAccessed: new Date(), - }, - ], - }, - ]; - - cookieJars.forEach(jar => { - const originalJar = { ...fakeBaseModel, ...jar }; - const sdkJar = new CookieObject(originalJar); - const convertedJar = mergeCookieJar(originalJar, sdkJar.jar().toInsomniaCookieJar()); - - expect(convertedJar).toEqual(originalJar); - }); + domain: 'inso.com', + path: '/', + secure: true, + httpOnly: true, + extensions: [], + creation: new Date(), + creationIndex: 0, + hostOnly: true, + pathIsDefault: true, + lastAccessed: new Date(), + }, + { + id: '2', + key: 'c2', + value: 'v2', + expires: new Date('08 Aug 1988 08:08:08 GMT'), + domain: 'inso.com', + path: '/', + secure: true, + httpOnly: true, + extensions: [], + creation: new Date(), + creationIndex: 0, + hostOnly: true, + pathIsDefault: true, + lastAccessed: new Date(), + }, + ], + }, + ]; + + cookieJars.forEach(jar => { + const originalJar = { ...fakeBaseModel, ...jar }; + const sdkJar = new CookieObject(originalJar); + const convertedJar = mergeCookieJar(originalJar, sdkJar.jar().toInsomniaCookieJar()); + + expect(convertedJar).toEqual(originalJar); }); + }); }); describe('test CookieJar', () => { - it('basic operations', () => { - const cookieOptBase = { - key: 'myCookie', - value: 'myCookie', - expires: '01 Jan 1970 00:00:01 GMT', - maxAge: 7, - domain: 'domain.com', - path: '/', - secure: true, - httpOnly: true, - hostOnly: true, - session: true, - extensions: [{ key: 'Ext', value: 'ExtValue' }], - }; + it('basic operations', () => { + const cookieOptBase = { + key: 'myCookie', + value: 'myCookie', + expires: '01 Jan 1970 00:00:01 GMT', + maxAge: 7, + domain: 'domain.com', + path: '/', + secure: true, + httpOnly: true, + hostOnly: true, + session: true, + extensions: [{ key: 'Ext', value: 'ExtValue' }], + }; - const jar = new CookieJar( - 'my jar', - [ - new Cookie({ ...cookieOptBase, key: 'c1', value: 'c1' }), - new Cookie({ ...cookieOptBase, key: 'c2', value: 'c2' }), - ], - ); + const jar = new CookieJar('my jar', [ + new Cookie({ ...cookieOptBase, key: 'c1', value: 'c1' }), + new Cookie({ ...cookieOptBase, key: 'c2', value: 'c2' }), + ]); - jar.set('domain.com', 'c1', { ...cookieOptBase, key: 'c1', value: 'c1Updated' }, (error, cookie) => { - expect(error).toBeUndefined(); - expect(cookie?.toJSON()).toEqual( - new Cookie({ ...cookieOptBase, key: 'c1', value: 'c1Updated' }).toJSON() - ); - }); - - jar.set('domain2.com', 'c2', { ...cookieOptBase, key: 'c2', value: 'c2' }, (error, cookie) => { - expect(error).toBeUndefined(); - expect(cookie?.toJSON()).toEqual( - new Cookie({ ...cookieOptBase, key: 'c2', value: 'c2' }).toJSON() - ); - }); - - jar.get('domain.com', 'c1', (err, cookie) => { - expect(err).toBeUndefined(); - expect( - cookie?.toJSON(), - ).toEqual( - new Cookie({ ...cookieOptBase, key: 'c1', value: 'c1Updated' }).toJSON(), - ); - }); - - jar.get('domain2.com', 'c2', (err, cookie) => { - expect(err).toBeUndefined(); - expect( - cookie?.toJSON(), - ).toEqual( - new Cookie({ ...cookieOptBase, key: 'c2', value: 'c2' }).toJSON(), - ); - }); - - jar.unset('domain.com', 'c1', err => { - expect(err).toBeUndefined(); - }); - - jar.get('domain.com', 'c1', (err, cookie) => { - expect(err).toBeUndefined(); - expect(cookie).toBeUndefined(); - }); - - jar.clear('domain2.com', err => { - expect(err).toBeUndefined(); - }); - - jar.get('domain2.com', 'c2', (err, cookie) => { - expect(err).toBeUndefined(); - expect(cookie).toBeUndefined(); - }); + jar.set('domain.com', 'c1', { ...cookieOptBase, key: 'c1', value: 'c1Updated' }, (error, cookie) => { + expect(error).toBeUndefined(); + expect(cookie?.toJSON()).toEqual(new Cookie({ ...cookieOptBase, key: 'c1', value: 'c1Updated' }).toJSON()); }); - it('CookieList operations', () => { - const cookieList = new CookieList( - [ - new Cookie({ key: 'c1', value: 'v1' }), - new Cookie({ key: 'c2', value: 'v2' }), - ] - ); - - const upsertedC1 = new Cookie({ key: 'c1', value: 'v1upserted' }); - cookieList.upsert(upsertedC1); - expect(cookieList.one('c1')).toEqual(upsertedC1); + jar.set('domain2.com', 'c2', { ...cookieOptBase, key: 'c2', value: 'c2' }, (error, cookie) => { + expect(error).toBeUndefined(); + expect(cookie?.toJSON()).toEqual(new Cookie({ ...cookieOptBase, key: 'c2', value: 'c2' }).toJSON()); }); + + jar.get('domain.com', 'c1', (err, cookie) => { + expect(err).toBeUndefined(); + expect(cookie?.toJSON()).toEqual(new Cookie({ ...cookieOptBase, key: 'c1', value: 'c1Updated' }).toJSON()); + }); + + jar.get('domain2.com', 'c2', (err, cookie) => { + expect(err).toBeUndefined(); + expect(cookie?.toJSON()).toEqual(new Cookie({ ...cookieOptBase, key: 'c2', value: 'c2' }).toJSON()); + }); + + jar.unset('domain.com', 'c1', err => { + expect(err).toBeUndefined(); + }); + + jar.get('domain.com', 'c1', (err, cookie) => { + expect(err).toBeUndefined(); + expect(cookie).toBeUndefined(); + }); + + jar.clear('domain2.com', err => { + expect(err).toBeUndefined(); + }); + + jar.get('domain2.com', 'c2', (err, cookie) => { + expect(err).toBeUndefined(); + expect(cookie).toBeUndefined(); + }); + }); + + it('CookieList operations', () => { + const cookieList = new CookieList([new Cookie({ key: 'c1', value: 'v1' }), new Cookie({ key: 'c2', value: 'v2' })]); + + const upsertedC1 = new Cookie({ key: 'c1', value: 'v1upserted' }); + cookieList.upsert(upsertedC1); + expect(cookieList.one('c1')).toEqual(upsertedC1); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/environments.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/environments.test.ts index d51517feb9..955c53c64f 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/environments.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/environments.test.ts @@ -5,104 +5,96 @@ import { Environment, Variables } from '../environments'; import { Folder, ParentFolders } from '../folders'; describe('test Variables object', () => { - it('test basic operations', () => { - const variables = new Variables({ - globalVars: new Environment('globals', { value: 'xyz' }), - environmentVars: new Environment('environments', {}), - collectionVars: new Environment('baseEnvironment', {}), - iterationDataVars: new Environment('iterationData', {}), - folderLevelVars: [], - localVars: new Environment('local', {}), - }); - - const uuidAndXyz = variables.replaceIn('{{ $randomUUID }}{{value }}'); - expect(validate(uuidAndXyz.replace('xyz', ''))).toBeTruthy(); - - const uuidAndBrackets1 = variables.replaceIn('{{ $randomUUID }}}}'); - expect(validate(uuidAndBrackets1.replace('}}', ''))).toBeTruthy(); - - const uuidAndBrackets2 = variables.replaceIn('}}{{ $randomUUID }}'); - expect(validate(uuidAndBrackets2.replace('}}', ''))).toBeTruthy(); + it('test basic operations', () => { + const variables = new Variables({ + globalVars: new Environment('globals', { value: 'xyz' }), + environmentVars: new Environment('environments', {}), + collectionVars: new Environment('baseEnvironment', {}), + iterationDataVars: new Environment('iterationData', {}), + folderLevelVars: [], + localVars: new Environment('local', {}), }); - it('test environment overriding', () => { - const globalOnlyVariables = new Variables({ - globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), - environmentVars: new Environment('environments', {}), - collectionVars: new Environment('baseEnvironment', {}), - iterationDataVars: new Environment('iterationData', {}), - folderLevelVars: [], - localVars: new Environment('local', {}), - }); - const normalVariables = new Variables({ - globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), - environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), - collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), - iterationDataVars: new Environment('iterationData', {}), - folderLevelVars: [], - localVars: new Environment('local', {}), - }); - const variablesWithIterationData = new Variables({ - globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), - environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), - collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), - iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), - folderLevelVars: [], - localVars: new Environment('local', {}), - }); - const variablesWithFolderLevelData = new Variables({ - globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), - environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), - collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), - iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), - folderLevelVars: [ - new Environment('folderLevel1', { scope: 'folderLevel1', value: 'folderLevel1-value' }), - new Environment('folderLevel2', { scope: 'folderLevel2', value: 'folderLevel2-value' }), - ], - localVars: new Environment('local', { scope: 'local' }), - }); - const variablesWithLocalData = new Variables({ - globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), - environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), - collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), - iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), - folderLevelVars: [], - localVars: new Environment('local', { scope: 'local', value: 'local-value' }), - }); + const uuidAndXyz = variables.replaceIn('{{ $randomUUID }}{{value }}'); + expect(validate(uuidAndXyz.replace('xyz', ''))).toBeTruthy(); - expect(globalOnlyVariables.get('value')).toEqual('global-value'); - expect(normalVariables.get('value')).toEqual('subEnv-value'); - expect(variablesWithIterationData.get('value')).toEqual('iterationData-value'); - expect(variablesWithFolderLevelData.get('value')).toEqual('folderLevel2-value'); - expect(variablesWithLocalData.get('value')).toEqual('local-value'); + const uuidAndBrackets1 = variables.replaceIn('{{ $randomUUID }}}}'); + expect(validate(uuidAndBrackets1.replace('}}', ''))).toBeTruthy(); - expect(variablesWithFolderLevelData.replaceIn('{{ value}}')).toEqual('folderLevel2-value'); + const uuidAndBrackets2 = variables.replaceIn('}}{{ $randomUUID }}'); + expect(validate(uuidAndBrackets2.replace('}}', ''))).toBeTruthy(); + }); + + it('test environment overriding', () => { + const globalOnlyVariables = new Variables({ + globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), + environmentVars: new Environment('environments', {}), + collectionVars: new Environment('baseEnvironment', {}), + iterationDataVars: new Environment('iterationData', {}), + folderLevelVars: [], + localVars: new Environment('local', {}), + }); + const normalVariables = new Variables({ + globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), + environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), + collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), + iterationDataVars: new Environment('iterationData', {}), + folderLevelVars: [], + localVars: new Environment('local', {}), + }); + const variablesWithIterationData = new Variables({ + globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), + environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), + collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), + iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), + folderLevelVars: [], + localVars: new Environment('local', {}), + }); + const variablesWithFolderLevelData = new Variables({ + globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), + environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), + collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), + iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), + folderLevelVars: [ + new Environment('folderLevel1', { scope: 'folderLevel1', value: 'folderLevel1-value' }), + new Environment('folderLevel2', { scope: 'folderLevel2', value: 'folderLevel2-value' }), + ], + localVars: new Environment('local', { scope: 'local' }), + }); + const variablesWithLocalData = new Variables({ + globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), + environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), + collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), + iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), + folderLevelVars: [], + localVars: new Environment('local', { scope: 'local', value: 'local-value' }), }); - it('variables operations', () => { - const folders = new ParentFolders([ - new Folder( - '1', - 'folder1', - { value: 'folder1Value' }, - ), - new Folder( - '2', - 'folder2', - { value: 'folder2Value' }, - ), - ]); + expect(globalOnlyVariables.get('value')).toEqual('global-value'); + expect(normalVariables.get('value')).toEqual('subEnv-value'); + expect(variablesWithIterationData.get('value')).toEqual('iterationData-value'); + expect(variablesWithFolderLevelData.get('value')).toEqual('folderLevel2-value'); + expect(variablesWithLocalData.get('value')).toEqual('local-value'); - const variables = new Variables({ - globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), - environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), - collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), - iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), - folderLevelVars: folders.getEnvironments(), - localVars: new Environment('local', { scope: 'local' }), - }); + expect(variablesWithFolderLevelData.replaceIn('{{ value}}')).toEqual('folderLevel2-value'); + }); - folders.get('folder2').environment.set('value', 'folder1ValueOverride'); - expect(variables.get('value')).toEqual('folder1ValueOverride'); + it('variables operations', () => { + const folders = new ParentFolders([ + new Folder('1', 'folder1', { value: 'folder1Value' }), + new Folder('2', 'folder2', { value: 'folder2Value' }), + ]); + + const variables = new Variables({ + globalVars: new Environment('globals', { scope: 'global', value: 'global-value' }), + environmentVars: new Environment('environments', { scope: 'subEnv', value: 'subEnv-value' }), + collectionVars: new Environment('baseEnvironment', { scope: 'baseEnv', value: 'baseEnv-value' }), + iterationDataVars: new Environment('iterationData', { scope: 'iterationData', value: 'iterationData-value' }), + folderLevelVars: folders.getEnvironments(), + localVars: new Environment('local', { scope: 'local' }), }); + + folders.get('folder2').environment.set('value', 'folder1ValueOverride'); + expect(variables.get('value')).toEqual('folder1ValueOverride'); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/headers.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/headers.test.ts index ceb77941ce..9bf7f5a8a0 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/headers.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/headers.test.ts @@ -4,31 +4,26 @@ import { Header, HeaderList } from '../headers'; // import { QueryParam, setUrlParser, Url, UrlMatchPattern } from '../urls'; describe('test Header object', () => { - it('test basic operations', () => { - // const header = new Header('Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n'); - const headerStr = 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n'; - const headerObjs = [ - { key: 'Content-Type', value: 'application/json' }, - { key: 'User-Agent', value: 'MyClientLibrary/2.0' }, - ]; + it('test basic operations', () => { + // const header = new Header('Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n'); + const headerStr = 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n'; + const headerObjs = [ + { key: 'Content-Type', value: 'application/json' }, + { key: 'User-Agent', value: 'MyClientLibrary/2.0' }, + ]; - expect(Header.parse(headerStr)).toEqual(headerObjs); - expect( - Header.parse(Header.unparse(headerObjs)) - ).toEqual(headerObjs); - }); + expect(Header.parse(headerStr)).toEqual(headerObjs); + expect(Header.parse(Header.unparse(headerObjs))).toEqual(headerObjs); + }); - it('HeaderList operations', () => { - const headerList = new HeaderList( - undefined, - [ - new Header({ key: 'h1', value: 'v1' }), - new Header({ key: 'h2', value: 'v2' }), - ] - ); + it('HeaderList operations', () => { + const headerList = new HeaderList(undefined, [ + new Header({ key: 'h1', value: 'v1' }), + new Header({ key: 'h2', value: 'v2' }), + ]); - const upserted = new Header({ key: 'h1', value: 'v1upserted' }); - headerList.upsert(upserted); - expect(headerList.one('h1')).toEqual(upserted); - }); + const upserted = new Header({ key: 'h1', value: 'v1upserted' }); + headerList.upsert(upserted); + expect(headerList.one('h1')).toEqual(upserted); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/properties.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/properties.test.ts index 86c7159bf7..e798ad8448 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/properties.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/properties.test.ts @@ -3,205 +3,134 @@ import { describe, expect, it } from 'vitest'; import { Property, PropertyBase, PropertyList } from '../properties'; describe('test Property objects', () => { + it('PropertyBase: basic operations', () => { + const pbase = new PropertyBase('my property'); - it('PropertyBase: basic operations', () => { - const pbase = new PropertyBase('my property'); + expect(pbase.toJSON()).toEqual({ + description: 'my property', + }); + expect(pbase.toObject()).toEqual({ + description: 'my property', + }); + }); - expect(pbase.toJSON()).toEqual({ - description: 'my property', - }); - expect(pbase.toObject()).toEqual({ - description: 'my property', - }); + it('Property: basic operations', () => { + const prop = new Property('id', 'name', false, { id: 'real_id', name: 'real_name' }); + + expect(prop.toJSON()).toEqual({ + disabled: false, + id: 'real_id', + name: 'real_name', }); - it('Property: basic operations', () => { - const prop = new Property( - 'id', - 'name', - false, - { id: 'real_id', name: 'real_name' }, - ); + expect(Property.replaceSubstitutions('{{ hehe }}', { hehe: 777 })).toEqual('777'); + expect( + Property.replaceSubstitutionsIn( + { + value: '{{ hehe }}', + }, + { hehe: 777 }, + ), + ).toEqual({ value: '777' }); + }); - expect(prop.toJSON()).toEqual({ - disabled: false, - id: 'real_id', - name: 'real_name', - }); + it('PropertyList: basic operations: add, append, count, all, clear', () => { + const propList = new PropertyList({}, undefined, [new Property('id1', 'p1')]); - expect(Property.replaceSubstitutions('{{ hehe }}', { hehe: 777 })).toEqual('777'); - expect(Property.replaceSubstitutionsIn( - { - value: '{{ hehe }}', - }, - { hehe: 777 }, - )) - .toEqual({ value: '777' }); - }); + propList.add(new Property('id2', 'p2')); + propList.append(new Property('id3', 'p3')); + expect(propList.count()).toBe(3); + expect(propList.all()).toEqual([ + { + disabled: false, + id: 'id1', + name: 'p1', + }, + { + disabled: false, + id: 'id2', + name: 'p2', + }, + { + disabled: false, + id: 'id3', + name: 'p3', + }, + ]); - it('PropertyList: basic operations: add, append, count, all, clear', () => { - const propList = new PropertyList( - {}, - undefined, - [ - new Property('id1', 'p1'), - ], - ); + propList.clear(); + }); - propList.add(new Property('id2', 'p2')); - propList.append(new Property('id3', 'p3')); - expect(propList.count()).toBe(3); - expect(propList.all()).toEqual([ - { - disabled: false, - id: 'id1', - name: 'p1', - }, - { - disabled: false, - id: 'id2', - name: 'p2', - }, - { - disabled: false, - id: 'id3', - name: 'p3', - }, - ]); + it('PropertyList: basic operations: assimilate, each, filter, find', () => { + const propList = new PropertyList(Property, undefined, []); - propList.clear(); - }); + propList.assimilate([new Property('id1', 'p1'), new Property('id2', 'p2')], false); + expect(propList.count()).toBe(2); - it('PropertyList: basic operations: assimilate, each, filter, find', () => { - const propList = new PropertyList( - Property, - undefined, - [], - ); + propList.each(prop => { + expect(prop.name?.startsWith('p')).toBeTruthy(); + }, {}); - propList.assimilate( - [ - new Property('id1', 'p1'), - new Property('id2', 'p2'), - ], - false, - ); - expect(propList.count()).toBe(2); + expect(propList.filter(prop => prop.name === 'p1', {}).length).toBe(1); - propList.each( - prop => { - expect(prop.name?.startsWith('p')).toBeTruthy(); - }, - {}, - ); + expect(propList.find(prop => prop?.name === 'p2', {}) != null).toBeTruthy(); + }); - expect( - propList.filter( - prop => prop.name === 'p1', - {}, - ).length - ).toBe(1); + it('PropertyList: basic operations: one, has, indexOf, insert, insertAfter, prepend, populate, map, reduce', () => { + const propList = new PropertyList(Property, undefined, [ + new Property('id1', 'p1'), + new Property('id2', 'p2'), + ]); - expect( - propList.find( - prop => prop?.name === 'p2', - {}, - ) != null - ).toBeTruthy(); - }); + expect(propList.one('id1')).toEqual(new Property('id1', 'p1')); + expect(propList.has(new Property('id1', 'p1'))).toBeTruthy(); + expect(propList.indexOf(new Property('id1', 'p1')) === 0).toBeTruthy(); + propList.clear(); - it('PropertyList: basic operations: one, has, indexOf, insert, insertAfter, prepend, populate, map, reduce', () => { - const propList = new PropertyList( - Property, - undefined, - [ - new Property('id1', 'p1'), - new Property('id2', 'p2'), - ], - ); + propList.insert(new Property('id0', 'p0'), 0); + propList.insertAfter(new Property('id1', 'p1'), 1); + propList.prepend(new Property('id-1', 'p-1')); + propList.populate([new Property('id2', 'p2')]); + }); - expect(propList.one('id1')) - .toEqual(new Property('id1', 'p1')); - expect(propList.has(new Property('id1', 'p1'))) - .toBeTruthy(); - expect(propList.indexOf(new Property('id1', 'p1')) === 0).toBeTruthy(); - propList.clear(); + it('PropertyList: basic operations: one, has, indexOf, insert, insertAfter, prepend, populate, map, reduce', () => { + const propList = new PropertyList(Property, undefined, [ + new Property('id0', 'p0'), + new Property('id1', 'p1'), + new Property('id2', 'p2'), + ]); - propList.insert(new Property('id0', 'p0'), 0); - propList.insertAfter(new Property('id1', 'p1'), 1); - propList.prepend(new Property('id-1', 'p-1')); - propList.populate([new Property('id2', 'p2')]); - }); + expect(propList.map(prop => prop.id, {})).toEqual(['id0', 'id1', 'id2']); + expect(propList.reduce((acc, prop) => (acc += prop.id), '', {})).toEqual('id0id1id2'); + }); - it('PropertyList: basic operations: one, has, indexOf, insert, insertAfter, prepend, populate, map, reduce', () => { - const propList = new PropertyList( - Property, - undefined, - [ - new Property('id0', 'p0'), - new Property('id1', 'p1'), - new Property('id2', 'p2'), - ], - ); + it('PropertyList: basic operations: remove, count, repopulate, toString, get, one, idx, upsert', () => { + const propList = new PropertyList(Property, undefined, [ + new Property('id0', 'p0'), + new Property('id1', 'p1'), + new Property('id2', 'p2'), + ]); - expect( - propList.map( - prop => prop.id, - {}, - ) - ).toEqual([ - 'id0', - 'id1', - 'id2', - ]); - expect( - propList.reduce( - (acc, prop) => acc += prop.id, - '', - {}, - ), - ).toEqual('id0id1id2'); - }); + propList.remove(prop => prop.id === 'id0', {}); + expect(propList.count()).toEqual(2); - it('PropertyList: basic operations: remove, count, repopulate, toString, get, one, idx, upsert', () => { - const propList = new PropertyList( - Property, - undefined, - [ - new Property('id0', 'p0'), - new Property('id1', 'p1'), - new Property('id2', 'p2'), - ], - ); + propList.repopulate([new Property('id1', 'p1'), new Property('id2', 'p2')]); - propList.remove( - prop => prop.id === 'id0', - {}, - ); - expect( - propList.count(), - ).toEqual(2); + expect(propList.toString()).toEqual( + '[{"id":"id1","name":"p1","disabled":false}; {"id":"id2","name":"p2","disabled":false}]', + ); - propList.repopulate([ - new Property('id1', 'p1'), - new Property('id2', 'p2'), - ]); + const expectedP1 = new Property('id1', 'p1'); + const getP1 = propList.get('id1'); + const oneP1 = propList.one('id1'); + expect(getP1).toEqual(expectedP1); + expect(oneP1).toEqual(expectedP1); - expect(propList.toString()).toEqual( - '[{"id":"id1","name":"p1","disabled":false}; {"id":"id2","name":"p2","disabled":false}]', - ); + const idxP1 = propList.idx(0); + expect(idxP1).toEqual(expectedP1); - const expectedP1 = new Property('id1', 'p1'); - const getP1 = propList.get('id1'); - const oneP1 = propList.one('id1'); - expect(getP1).toEqual(expectedP1); - expect(oneP1).toEqual(expectedP1); - - const idxP1 = propList.idx(0); - expect(idxP1).toEqual(expectedP1); - - const upsertedP2 = new Property('id2', 'upsertedP2'); - propList.upsert(upsertedP2); - expect(propList.one('id2')).toEqual(upsertedP2); - }); + const upsertedP2 = new Property('id2', 'upsertedP2'); + propList.upsert(upsertedP2); + expect(propList.one('id2')).toEqual(upsertedP2); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/proxy-configs.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/proxy-configs.test.ts index 7469cbdf19..977ffb8ce2 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/proxy-configs.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/proxy-configs.test.ts @@ -4,65 +4,58 @@ import { ProxyConfig, ProxyConfigList, transformToSdkProxyOptions } from '../pro import { Url } from '../urls'; describe('test ProxyConfig object', () => { - it('test basic operations', () => { - - const proxyConfig = new ProxyConfig({ - match: 'http+https://*.example.com:80/*', - host: 'proxy.com', - port: 8080, - tunnel: true, - disabled: false, - authenticate: true, - username: 'proxy_username', - password: 'proxy_password', - protocol: 'https:', - }); - - expect( - proxyConfig.getProtocols() - ).toEqual( - ['http', 'https'] - ); - - expect(proxyConfig.getProxyUrl()).toEqual( - 'https://proxy_username:proxy_password@proxy.com:8080' - ); - - expect( - proxyConfig.test('http://a.example.com:80/a') - ).toBeTruthy(); - - const configList = new ProxyConfigList(undefined, []); - configList.add(proxyConfig); - configList.add(new ProxyConfig({ - match: 'https://*.example.com:80/*', - host: 'proxy.com', - port: 8080, - tunnel: true, - disabled: false, - authenticate: true, - username: 'proxy_username', - password: 'proxy_password', - protocol: 'https:', - })); - - const matchedProxyConfigDef = configList.resolve(new Url('http://sub.example.com:80/path')); - expect(matchedProxyConfigDef?.host).toEqual('proxy.com'); + it('test basic operations', () => { + const proxyConfig = new ProxyConfig({ + match: 'http+https://*.example.com:80/*', + host: 'proxy.com', + port: 8080, + tunnel: true, + disabled: false, + authenticate: true, + username: 'proxy_username', + password: 'proxy_password', + protocol: 'https:', }); - const proxyUrls = [ - 'http://wormhole', - 'http://wormhole:0', - 'https://localhost', - 'http://user:pass@localhost:666', - 'http://user:pass@localhost:0', - 'http://user:pass@localhost', - ]; + expect(proxyConfig.getProtocols()).toEqual(['http', 'https']); - proxyUrls.forEach(url => { - it(`test proxy transforming: ${url}`, () => { - const proxy = new ProxyConfig(transformToSdkProxyOptions(url, '', true, '')); - expect(proxy.getProxyUrl()).toEqual(url); - }); + expect(proxyConfig.getProxyUrl()).toEqual('https://proxy_username:proxy_password@proxy.com:8080'); + + expect(proxyConfig.test('http://a.example.com:80/a')).toBeTruthy(); + + const configList = new ProxyConfigList(undefined, []); + configList.add(proxyConfig); + configList.add( + new ProxyConfig({ + match: 'https://*.example.com:80/*', + host: 'proxy.com', + port: 8080, + tunnel: true, + disabled: false, + authenticate: true, + username: 'proxy_username', + password: 'proxy_password', + protocol: 'https:', + }), + ); + + const matchedProxyConfigDef = configList.resolve(new Url('http://sub.example.com:80/path')); + expect(matchedProxyConfigDef?.host).toEqual('proxy.com'); + }); + + const proxyUrls = [ + 'http://wormhole', + 'http://wormhole:0', + 'https://localhost', + 'http://user:pass@localhost:666', + 'http://user:pass@localhost:0', + 'http://user:pass@localhost', + ]; + + proxyUrls.forEach(url => { + it(`test proxy transforming: ${url}`, () => { + const proxy = new ProxyConfig(transformToSdkProxyOptions(url, '', true, '')); + expect(proxy.getProxyUrl()).toEqual(url); }); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/request.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/request.test.ts index 0d92c419c4..e4f3897c69 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/request.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/request.test.ts @@ -1,156 +1,154 @@ import { describe, expect, it } from 'vitest'; -import type { Header} from '../headers'; +import type { Header } from '../headers'; import { HeaderList } from '../headers'; -import type { RequestBodyOptions} from '../request'; +import type { RequestBodyOptions } from '../request'; import { calculatePayloadSize, mergeRequestBody, Request, RequestBody, toScriptRequestBody } from '../request'; describe('test request and response objects', () => { - it('test RequestBody methods', () => { - const reqBody = new RequestBody({ - mode: 'urlencoded', - formdata: [ - { key: 'formDataKey', value: 'formDataValue' }, - ], - urlencoded: [ - { key: 'urlencodedKey', value: 'urlencodedValue' }, - ], - options: {}, - }); - - expect(reqBody.toString()).toEqual('urlencodedKey=urlencodedValue'); - - reqBody.update({ mode: 'file', file: 'file content here' }); - expect(reqBody.toString()).toEqual('file content here'); + it('test RequestBody methods', () => { + const reqBody = new RequestBody({ + mode: 'urlencoded', + formdata: [{ key: 'formDataKey', value: 'formDataValue' }], + urlencoded: [{ key: 'urlencodedKey', value: 'urlencodedValue' }], + options: {}, }); - it('test Request methods', () => { - const req = new Request({ - name: 'myReq', - url: 'https://hostname.com/path', - method: 'GET', - header: [ - { key: 'header1', value: 'val1' }, - { key: 'header2', value: 'val2' }, - ], - body: { - mode: 'raw', - raw: 'body content', - }, - auth: { - type: 'basic', - basic: [ - { key: 'username', value: 'myname' }, - { key: 'password', value: 'mypwd' }, - ], - }, - proxy: undefined, - certificate: undefined, - }); + expect(reqBody.toString()).toEqual('urlencodedKey=urlencodedValue'); - expect(req.name).toEqual('myReq'); + reqBody.update({ mode: 'file', file: 'file content here' }); + expect(reqBody.toString()).toEqual('file content here'); + }); - req.addHeader({ key: 'newHeader', value: 'newValue' }); - expect(req.headers.count()).toEqual(3); - req.removeHeader('notExist', { ignoreCase: false }); - expect(req.headers.count()).toEqual(3); - req.removeHeader('NEWHEADER', { ignoreCase: false }); - expect(req.headers.count()).toEqual(3); - req.removeHeader('NEWHEADER', { ignoreCase: true }); - expect(req.headers.count()).toEqual(2); - - req.upsertHeader({ key: 'header1', value: 'new_val1' }); - expect(req.getHeaders({ - ignoreCase: true, - enabled: true, - multiValue: true, - sanitizeKeys: true, - })).toEqual({ - header1: ['new_val1'], - header2: ['val2'], - }); - - const req2 = req.clone(); - expect(req2.toJSON()).toEqual(req.toJSON()); + it('test Request methods', () => { + const req = new Request({ + name: 'myReq', + url: 'https://hostname.com/path', + method: 'GET', + header: [ + { key: 'header1', value: 'val1' }, + { key: 'header2', value: 'val2' }, + ], + body: { + mode: 'raw', + raw: 'body content', + }, + auth: { + type: 'basic', + basic: [ + { key: 'username', value: 'myname' }, + { key: 'password', value: 'mypwd' }, + ], + }, + proxy: undefined, + certificate: undefined, }); - it('test Request body transforming', () => { - const bodies = [ - { - mimeType: 'text/plain', - text: 'rawContent', - }, - { - mimeType: 'application/octet-stream', - fileName: 'path/to/file', - }, - { - mimeType: 'application/x-www-form-urlencoded', - params: [ - { name: 'k1', value: 'v1' }, - { name: 'k2', value: 'v2' }, - ], - }, - { - mimeType: 'application/json', - text: `{ + expect(req.name).toEqual('myReq'); + + req.addHeader({ key: 'newHeader', value: 'newValue' }); + expect(req.headers.count()).toEqual(3); + req.removeHeader('notExist', { ignoreCase: false }); + expect(req.headers.count()).toEqual(3); + req.removeHeader('NEWHEADER', { ignoreCase: false }); + expect(req.headers.count()).toEqual(3); + req.removeHeader('NEWHEADER', { ignoreCase: true }); + expect(req.headers.count()).toEqual(2); + + req.upsertHeader({ key: 'header1', value: 'new_val1' }); + expect( + req.getHeaders({ + ignoreCase: true, + enabled: true, + multiValue: true, + sanitizeKeys: true, + }), + ).toEqual({ + header1: ['new_val1'], + header2: ['val2'], + }); + + const req2 = req.clone(); + expect(req2.toJSON()).toEqual(req.toJSON()); + }); + + it('test Request body transforming', () => { + const bodies = [ + { + mimeType: 'text/plain', + text: 'rawContent', + }, + { + mimeType: 'application/octet-stream', + fileName: 'path/to/file', + }, + { + mimeType: 'application/x-www-form-urlencoded', + params: [ + { name: 'k1', value: 'v1' }, + { name: 'k2', value: 'v2' }, + ], + }, + { + mimeType: 'application/json', + text: `{ query: 'query', operationName: 'operation', variables: 'var', }`, - }, - { - mimeType: 'image/gif', - fileName: '/path/to/image', - }, - { - mimeType: 'multipart/form-data', - params: [ - { name: 'k1', type: 'text', value: 'v1' }, - { name: 'k2', type: 'file', value: '/path/to/image' }, - ], - }, - ]; - - bodies.forEach(body => { - const originalReqBody = body; - const scriptReqBody = new RequestBody(toScriptRequestBody(body)); - expect(mergeRequestBody(scriptReqBody, originalReqBody)).toEqual(originalReqBody); - }); - }); - - const reqBodyTestCases: { body: RequestBodyOptions; headers: HeaderList
; expectedTotal: number }[] = [ - { - body: { - mode: 'raw', - raw: '1', - }, - headers: new HeaderList
(undefined, []), - expectedTotal: 1, - }, - { - body: { - mode: 'raw', - raw: '😎', - }, - headers: new HeaderList
(undefined, []), - expectedTotal: 4, - }, - { - body: { - mode: 'raw', - raw: '睡', - }, - headers: new HeaderList
(undefined, []), - expectedTotal: 3, - }, + }, + { + mimeType: 'image/gif', + fileName: '/path/to/image', + }, + { + mimeType: 'multipart/form-data', + params: [ + { name: 'k1', type: 'text', value: 'v1' }, + { name: 'k2', type: 'file', value: '/path/to/image' }, + ], + }, ]; - reqBodyTestCases.forEach(({ body, headers, expectedTotal }) => { - it(`test calculatePayloadSize: ${body.raw}`, () => { - const reqSize = calculatePayloadSize(new RequestBody(body).toString(), headers); - - expect(reqSize.total).toEqual(expectedTotal); - }); + bodies.forEach(body => { + const originalReqBody = body; + const scriptReqBody = new RequestBody(toScriptRequestBody(body)); + expect(mergeRequestBody(scriptReqBody, originalReqBody)).toEqual(originalReqBody); }); + }); + + const reqBodyTestCases: { body: RequestBodyOptions; headers: HeaderList
; expectedTotal: number }[] = [ + { + body: { + mode: 'raw', + raw: '1', + }, + headers: new HeaderList
(undefined, []), + expectedTotal: 1, + }, + { + body: { + mode: 'raw', + raw: '😎', + }, + headers: new HeaderList
(undefined, []), + expectedTotal: 4, + }, + { + body: { + mode: 'raw', + raw: '睡', + }, + headers: new HeaderList
(undefined, []), + expectedTotal: 3, + }, + ]; + + reqBodyTestCases.forEach(({ body, headers, expectedTotal }) => { + it(`test calculatePayloadSize: ${body.raw}`, () => { + const reqSize = calculatePayloadSize(new RequestBody(body).toString(), headers); + + expect(reqSize.total).toEqual(expectedTotal); + }); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/response.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/response.test.ts index e595796a7c..a1fd91417f 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/response.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/response.test.ts @@ -4,91 +4,91 @@ import { Request } from '../request'; import { Response } from '../response'; describe('test request and response objects', () => { - it('test Response methods', () => { - const req = new Request({ - url: 'https://hostname.com/path', - method: 'GET', - header: [ - { key: 'header1', value: 'val1' }, - { key: 'header2', value: 'val2' }, - ], - body: { - mode: 'raw', - raw: '{"key": 888}', - }, - auth: { - type: 'basic', - basic: [ - { key: 'username', value: 'myname' }, - { key: 'password', value: 'mypwd' }, - ], - }, - proxy: undefined, - certificate: undefined, - }); - - const resp = new Response({ - code: 200, - reason: 'OK', - header: [ - { key: 'header1', value: 'val1' }, - { key: 'header2', value: 'val2' }, - { key: 'Content-Length', value: '100' }, - { key: 'Content-Disposition', value: 'attachment; filename="filename.txt"' }, - { key: 'Content-Type', value: 'text/plain; charset=utf-8' }, - ], - cookie: [ - { key: 'header1', value: 'val1' }, - { key: 'header2', value: 'val2' }, - ], - body: '{"key": 888}', - stream: undefined, - responseTime: 100, - originalRequest: req, - }); - - // TODO: this will work after PropertyList.one is improved - // expect(resp.size()).toBe(100); - - expect(resp.json()).toEqual({ - key: 888, - }); - expect(resp.contentInfo()).toEqual({ - charset: 'utf-8', - contentType: 'text/plain; charset=utf-8', - fileExtension: 'txt', - fileName: 'filename', - mimeFormat: '', - mimeType: 'text/plain', - }); - - // extended assertion chains - resp.to.have.status(200); - resp.to.have.status('OK'); - resp.to.have.header('header1'); - resp.to.have.jsonBody({ 'key': 888 }); - resp.to.have.body('{"key": 888}'); - resp.to.have.jsonSchema({ - type: 'object', - properties: { - key: { type: 'integer' }, - }, - required: ['key'], - additionalProperties: false, - }); - - resp.to.not.have.status(201); - resp.to.not.have.status('NOT FOUND'); - resp.to.not.have.header('header_nonexist'); - resp.to.not.have.jsonBody({ 'key': 777 }); - resp.to.not.have.body('{"key": 777}'); - resp.to.not.have.jsonSchema({ - type: 'object', - properties: { - keyNoExist: { type: 'integer' }, - }, - required: ['keyNoExist'], - additionalProperties: false, - }); + it('test Response methods', () => { + const req = new Request({ + url: 'https://hostname.com/path', + method: 'GET', + header: [ + { key: 'header1', value: 'val1' }, + { key: 'header2', value: 'val2' }, + ], + body: { + mode: 'raw', + raw: '{"key": 888}', + }, + auth: { + type: 'basic', + basic: [ + { key: 'username', value: 'myname' }, + { key: 'password', value: 'mypwd' }, + ], + }, + proxy: undefined, + certificate: undefined, }); + + const resp = new Response({ + code: 200, + reason: 'OK', + header: [ + { key: 'header1', value: 'val1' }, + { key: 'header2', value: 'val2' }, + { key: 'Content-Length', value: '100' }, + { key: 'Content-Disposition', value: 'attachment; filename="filename.txt"' }, + { key: 'Content-Type', value: 'text/plain; charset=utf-8' }, + ], + cookie: [ + { key: 'header1', value: 'val1' }, + { key: 'header2', value: 'val2' }, + ], + body: '{"key": 888}', + stream: undefined, + responseTime: 100, + originalRequest: req, + }); + + // TODO: this will work after PropertyList.one is improved + // expect(resp.size()).toBe(100); + + expect(resp.json()).toEqual({ + key: 888, + }); + expect(resp.contentInfo()).toEqual({ + charset: 'utf-8', + contentType: 'text/plain; charset=utf-8', + fileExtension: 'txt', + fileName: 'filename', + mimeFormat: '', + mimeType: 'text/plain', + }); + + // extended assertion chains + resp.to.have.status(200); + resp.to.have.status('OK'); + resp.to.have.header('header1'); + resp.to.have.jsonBody({ key: 888 }); + resp.to.have.body('{"key": 888}'); + resp.to.have.jsonSchema({ + type: 'object', + properties: { + key: { type: 'integer' }, + }, + required: ['key'], + additionalProperties: false, + }); + + resp.to.not.have.status(201); + resp.to.not.have.status('NOT FOUND'); + resp.to.not.have.header('header_nonexist'); + resp.to.not.have.jsonBody({ key: 777 }); + resp.to.not.have.body('{"key": 777}'); + resp.to.not.have.jsonSchema({ + type: 'object', + properties: { + keyNoExist: { type: 'integer' }, + }, + required: ['keyNoExist'], + additionalProperties: false, + }); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/urls.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/urls.test.ts index a4f34b44a6..66e14882cb 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/urls.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/urls.test.ts @@ -4,345 +4,342 @@ import { QueryParam, Url, UrlMatchPattern } from '../urls'; import { Variable } from '../variables'; describe('test Url object', () => { - it('test QueryParam', () => { - const queryParam = new QueryParam({ - key: 'uname', - value: 'patrick star', - }); - - expect(queryParam.toString()).toEqual('uname=patrick+star'); - - queryParam.update('uname=peter+parker'); - expect(queryParam.toString()).toEqual('uname=peter+parker'); - - expect( - QueryParam.unparseSingle({ key: 'uname', value: 'patrick star' }) - ).toEqual('uname=patrick+star'); - - expect( - QueryParam.unparse({ uname: 'patrick star', password: '123' }) - ).toEqual('uname=patrick+star&password=123'); - - expect( - QueryParam.parseSingle('uname=patrick+star') - ).toEqual({ key: 'uname', value: 'patrick star' }); - - expect( - QueryParam.parse('uname=patrick+star&password=123') - ).toEqual([{ 'key': 'uname', 'value': 'patrick star' }, { 'key': 'password', 'value': '123' }]); + it('test QueryParam', () => { + const queryParam = new QueryParam({ + key: 'uname', + value: 'patrick star', }); - it('test Url methods', () => { - const url = new Url({ - auth: { - username: 'usernameValue', - password: 'passwordValue', - }, - hash: 'hashValue', - host: ['hostValue', 'com'], - path: ['pathLevel1', 'pathLevel2'], - port: '777', - protocol: 'https:', - query: [ - new QueryParam({ key: 'key1', value: 'value1' }), - new QueryParam({ key: 'key2', value: 'value2' }), - new QueryParam({ key: 'key3', value: 'value3' }), - ], - variables: [ - new Variable({ key: 'varKey', value: 'varValue' }), - ], - }); + expect(queryParam.toString()).toEqual('uname=patrick+star'); - // expect(url.getHost()).toEqual('hostValue.com'); - expect(url.getPath()).toEqual('/pathLevel1/pathLevel2'); + queryParam.update('uname=peter+parker'); + expect(queryParam.toString()).toEqual('uname=peter+parker'); - expect(url.getQueryString()).toEqual('key1=value1&key2=value2&key3=value3'); - expect(url.getPathWithQuery()).toEqual('/pathLevel1/pathLevel2?key1=value1&key2=value2&key3=value3'); - expect(url.getRemote(true)).toEqual('hostvalue.com:777'); - expect(url.getRemote(false)).toEqual('hostvalue.com:777'); // TODO: add more cases + expect(QueryParam.unparseSingle({ key: 'uname', value: 'patrick star' })).toEqual('uname=patrick+star'); - url.removeQueryParams([ - new QueryParam({ key: 'key1', value: 'value1' }), - ]); - url.removeQueryParams('key3'); - expect(url.getQueryString()).toEqual('key2=value2'); - expect(url.toString()).toEqual('https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue'); + expect(QueryParam.unparse({ uname: 'patrick star', password: '123' })).toEqual('uname=patrick+star&password=123'); - const url2 = new Url('https://usernameValue:passwordValue@hostValue.com:777/pathLevel1/pathLevel2?key1=value1&key2=value2#hashValue'); - expect(url2.getHost()).toEqual('hostvalue.com'); - expect(url2.getPath()).toEqual('/pathLevel1/pathLevel2'); - expect(url2.getQueryString()).toEqual('key1=value1&key2=value2'); - expect(url2.getPathWithQuery()).toEqual('/pathLevel1/pathLevel2?key1=value1&key2=value2'); - expect(url2.getRemote(true)).toEqual('hostvalue.com:777'); - expect(url2.getRemote(false)).toEqual('hostvalue.com:777'); // TODO: add more cases + expect(QueryParam.parseSingle('uname=patrick+star')).toEqual({ key: 'uname', value: 'patrick star' }); - url2.removeQueryParams([ - new QueryParam({ key: 'key1', value: 'value1' }), - ]); - expect(url2.getQueryString()).toEqual('key2=value2'); - expect(url2.toString()).toEqual('https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue'); + expect(QueryParam.parse('uname=patrick+star&password=123')).toEqual([ + { key: 'uname', value: 'patrick star' }, + { key: 'password', value: '123' }, + ]); + }); + + it('test Url methods', () => { + const url = new Url({ + auth: { + username: 'usernameValue', + password: 'passwordValue', + }, + hash: 'hashValue', + host: ['hostValue', 'com'], + path: ['pathLevel1', 'pathLevel2'], + port: '777', + protocol: 'https:', + query: [ + new QueryParam({ key: 'key1', value: 'value1' }), + new QueryParam({ key: 'key2', value: 'value2' }), + new QueryParam({ key: 'key3', value: 'value3' }), + ], + variables: [new Variable({ key: 'varKey', value: 'varValue' })], }); - it('test Url static methods', () => { - // static methods - const urlStr = 'https://myhost.com/path1/path2'; - const urlOptions = Url.parse(urlStr); - const urlObj = new Url(urlOptions || ''); + // expect(url.getHost()).toEqual('hostValue.com'); + expect(url.getPath()).toEqual('/pathLevel1/pathLevel2'); - expect(urlObj.toString()).toEqual(urlStr); + expect(url.getQueryString()).toEqual('key1=value1&key2=value2&key3=value3'); + expect(url.getPathWithQuery()).toEqual('/pathLevel1/pathLevel2?key1=value1&key2=value2&key3=value3'); + expect(url.getRemote(true)).toEqual('hostvalue.com:777'); + expect(url.getRemote(false)).toEqual('hostvalue.com:777'); // TODO: add more cases + + url.removeQueryParams([new QueryParam({ key: 'key1', value: 'value1' })]); + url.removeQueryParams('key3'); + expect(url.getQueryString()).toEqual('key2=value2'); + expect(url.toString()).toEqual( + 'https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue', + ); + + const url2 = new Url( + 'https://usernameValue:passwordValue@hostValue.com:777/pathLevel1/pathLevel2?key1=value1&key2=value2#hashValue', + ); + expect(url2.getHost()).toEqual('hostvalue.com'); + expect(url2.getPath()).toEqual('/pathLevel1/pathLevel2'); + expect(url2.getQueryString()).toEqual('key1=value1&key2=value2'); + expect(url2.getPathWithQuery()).toEqual('/pathLevel1/pathLevel2?key1=value1&key2=value2'); + expect(url2.getRemote(true)).toEqual('hostvalue.com:777'); + expect(url2.getRemote(false)).toEqual('hostvalue.com:777'); // TODO: add more cases + + url2.removeQueryParams([new QueryParam({ key: 'key1', value: 'value1' })]); + expect(url2.getQueryString()).toEqual('key2=value2'); + expect(url2.toString()).toEqual( + 'https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue', + ); + }); + + it('test Url static methods', () => { + // static methods + const urlStr = 'https://myhost.com/path1/path2'; + const urlOptions = Url.parse(urlStr); + const urlObj = new Url(urlOptions || ''); + + expect(urlObj.toString()).toEqual(urlStr); + }); + + it('test Url property accessing', () => { + const urlStr = 'https://user:pwd@hehe.com:6666/path1/path2?q1=@&q2=:#myHash'; + const urlObj = new Url(urlStr); + + expect(urlObj.auth).toEqual({ username: 'user', password: 'pwd' }); + expect(urlObj.hash).toEqual('myHash'); + expect(urlObj.host).toEqual(['hehe', 'com']); + expect(urlObj.path).toEqual(['path1', 'path2']); + expect(urlObj.port).toEqual('6666'); + expect(urlObj.protocol).toEqual('https:'); + + const queryParams = urlObj.query.toObject(); + expect(queryParams[0].key).toEqual('q1'); + expect(queryParams[0].value).toEqual('@'); + expect(queryParams[1].key).toEqual('q2'); + expect(queryParams[1].value).toEqual(':'); + }); + + const urlParsingTests = [ + { + testName: 'interal url', + url: 'inso/', + }, + { + testName: 'interal url with protocol', + url: 'http://inso/', + }, + { + testName: 'interal url with auth', + url: 'http://name:pwd@inso/', + }, + { + testName: 'interal url with auth without protocol', + url: 'name:pwd@inso/', + }, + { + testName: 'ip address', + url: 'http://127.0.0.1/', + }, + { + testName: 'localhost', + url: 'https://localhost/', + }, + { + testName: 'url with query params', + url: 'localhost/?k=v', + }, + { + testName: 'url with hash', + url: 'localhost/#myHash', + }, + { + testName: 'url with query params and hash', + url: 'localhost/?k=v#myHash', + }, + { + testName: 'url with query params and hash', + url: 'localhost/?k={{ myValue }}', + }, + { + testName: 'url with query params and hash', + url: 'localhost/#My{{ hashValue }}', + }, + { + testName: 'url with path params', + url: 'inso.com/:path1/:path', + }, + { + testName: 'url with tags and path params', + url: '{{ _.baseUrl }}/:path1/:path', + }, + { + testName: 'hybrid of path params and tags', + url: '{{ baseUrl }}/:path_{{ _.pathSuffix }}', + }, + { + testName: '@ is used in path', + url: '{{ baseUrl }}/tom@any.com', + }, + { + testName: '@ is used in auth and path', + url: 'user:pass@a.com/tom@any.com', + }, + { + testName: '@ is used in auth', + url: 'user:pass@a.com/', + }, + { + testName: '@ is used in path with path params, targs and hash', + url: '{{ baseUrl }}/:path__{{ _.pathSuffix }}/tom@any.com#hash', + }, + ]; + + urlParsingTests.forEach(testCase => { + it(`parsing url: ${testCase.testName}`, () => { + const urlObj = new Url(testCase.url); + expect(urlObj.toString()).toEqual(testCase.url); }); + }); - it('test Url property accessing', () => { - const urlStr = 'https://user:pwd@hehe.com:6666/path1/path2?q1=@&q2=:#myHash'; - const urlObj = new Url(urlStr); + const additionalCases = [ + { + origin: 'http://{{ urlWithTagOnly}}', + expected: 'http://{{ urlWithTagOnly}}', + }, + { + origin: "http://httpbin.org/{{ method}}/{% uuid 'v4' %}", + expected: "http://httpbin.org/{{ method}}/{% uuid 'v4' %}", + }, + { + origin: 'my-domain', + expected: 'my-domain', + }, + { + origin: 'http://my-domain', + expected: 'http://my-domain', + }, + { + origin: 'https://youdomain/api/validateuser/abc@.contos.com', + expected: 'https://youdomain/api/validateuser/abc@.contos.com', + }, + { + origin: + 'https://s3.amazonaws.com/finance-department-bucket/2022/tax-certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3SGQVQG7FGA6KKA6/20221104/us-east-1/s3/aws4_request&X-Amz-Date=20221104T140227Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787314fd6da4d55', + expected: + 'https://s3.amazonaws.com/finance-department-bucket/2022/tax-certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3SGQVQG7FGA6KKA6/20221104/us-east-1/s3/aws4_request&X-Amz-Date=20221104T140227Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787314fd6da4d55', + }, + { + origin: 'https://hehe.com', + expected: 'https://hehe.com', + }, + { + origin: 'https://2001:db8:3333:4444:5555:6666:7777:8888', + expected: 'https://2001:db8:3333:4444:5555:6666:7777:8888', + }, + { + origin: 'http://127.0.0.1:6666', + expected: 'http://127.0.0.1:6666', + }, + { + origin: 'http://ihave@:inhostname.com', + expected: 'http://ihave@:inhostname.com', + }, + { + origin: "https://{{ _['examplehost']}}", + expected: "https://{{ _['examplehost']}}", + }, + { + origin: "http://{{ _['a']['b']['c']['url'] }}", + expected: "http://{{ _['a']['b']['c']['url'] }}", + }, + { + origin: 'invalid?id=@:/&name=张三', + expected: 'invalid?id=@:/&name=张三', + }, + ]; - expect(urlObj.auth).toEqual({ username: 'user', password: 'pwd' }); - expect(urlObj.hash).toEqual('myHash'); - expect(urlObj.host).toEqual(['hehe', 'com']); - expect(urlObj.path).toEqual(['path1', 'path2']); - expect(urlObj.port).toEqual('6666'); - expect(urlObj.protocol).toEqual('https:'); - - const queryParams = urlObj.query.toObject(); - expect(queryParams[0].key).toEqual('q1'); - expect(queryParams[0].value).toEqual('@'); - expect(queryParams[1].key).toEqual('q2'); - expect(queryParams[1].value).toEqual(':'); - }); - - const urlParsingTests = [ - { - testName: 'interal url', - url: 'inso/', - }, - { - testName: 'interal url with protocol', - url: 'http://inso/', - }, - { - testName: 'interal url with auth', - url: 'http://name:pwd@inso/', - }, - { - testName: 'interal url with auth without protocol', - url: 'name:pwd@inso/', - }, - { - testName: 'ip address', - url: 'http://127.0.0.1/', - }, - { - testName: 'localhost', - url: 'https://localhost/', - }, - { - testName: 'url with query params', - url: 'localhost/?k=v', - }, - { - testName: 'url with hash', - url: 'localhost/#myHash', - }, - { - testName: 'url with query params and hash', - url: 'localhost/?k=v#myHash', - }, - { - testName: 'url with query params and hash', - url: 'localhost/?k={{ myValue }}', - }, - { - testName: 'url with query params and hash', - url: 'localhost/#My{{ hashValue }}', - }, - { - testName: 'url with path params', - url: 'inso.com/:path1/:path', - }, - { - testName: 'url with tags and path params', - url: '{{ _.baseUrl }}/:path1/:path', - }, - { - testName: 'hybrid of path params and tags', - url: '{{ baseUrl }}/:path_{{ _.pathSuffix }}', - }, - { - testName: '@ is used in path', - url: '{{ baseUrl }}/tom@any.com', - }, - { - testName: '@ is used in auth and path', - url: 'user:pass@a.com/tom@any.com', - }, - { - testName: '@ is used in auth', - url: 'user:pass@a.com/', - }, - { - testName: '@ is used in path with path params, targs and hash', - url: '{{ baseUrl }}/:path__{{ _.pathSuffix }}/tom@any.com#hash', - }, - ]; - - urlParsingTests.forEach(testCase => { - it(`parsing url: ${testCase.testName}`, () => { - const urlObj = new Url(testCase.url); - expect(urlObj.toString()).toEqual(testCase.url); - }); - }); - - const additionalCases = [ - { - origin: 'http://{{ urlWithTagOnly}}', - expected: 'http://{{ urlWithTagOnly}}', - }, - { - origin: "http://httpbin.org/{{ method}}/{% uuid 'v4' %}", - expected: "http://httpbin.org/{{ method}}/{% uuid 'v4' %}", - }, - { - origin: 'my-domain', - expected: 'my-domain', - }, - { - origin: 'http://my-domain', - expected: 'http://my-domain', - }, - { - origin: 'https://youdomain/api/validateuser/abc@.contos.com', - expected: 'https://youdomain/api/validateuser/abc@.contos.com', - }, - { - origin: 'https://s3.amazonaws.com/finance-department-bucket/2022/tax-certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3SGQVQG7FGA6KKA6/20221104/us-east-1/s3/aws4_request&X-Amz-Date=20221104T140227Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787314fd6da4d55', - expected: 'https://s3.amazonaws.com/finance-department-bucket/2022/tax-certificate.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3SGQVQG7FGA6KKA6/20221104/us-east-1/s3/aws4_request&X-Amz-Date=20221104T140227Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=b228dbec8c1008c80c162e1210e4503dceead1e4d4751b4d9787314fd6da4d55', - }, - { - origin: 'https://hehe.com', - expected: 'https://hehe.com', - }, - { - origin: 'https://2001:db8:3333:4444:5555:6666:7777:8888', - expected: 'https://2001:db8:3333:4444:5555:6666:7777:8888', - }, - { - origin: 'http://127.0.0.1:6666', - expected: 'http://127.0.0.1:6666', - }, - { - origin: 'http://ihave@:inhostname.com', - expected: 'http://ihave@:inhostname.com', - }, - { - origin: "https://{{ _['examplehost']}}", - expected: "https://{{ _['examplehost']}}", - }, - { - origin: "http://{{ _['a']['b']['c']['url'] }}", - expected: "http://{{ _['a']['b']['c']['url'] }}", - }, - { - origin: 'invalid?id=@:/&name=张三', - expected: 'invalid?id=@:/&name=张三', - }, - ]; - - additionalCases.forEach(testCase => { - it(`parsing url: ${testCase.origin}`, () => { - const urlObj = new Url(testCase.origin); - urlObj.addQueryParams([{ key: 'key', value: 'value' }]); - urlObj.removeQueryParams('key'); - expect(urlObj.toString()).toEqual(testCase.expected); - }); + additionalCases.forEach(testCase => { + it(`parsing url: ${testCase.origin}`, () => { + const urlObj = new Url(testCase.origin); + urlObj.addQueryParams([{ key: 'key', value: 'value' }]); + urlObj.removeQueryParams('key'); + expect(urlObj.toString()).toEqual(testCase.expected); }); + }); }); describe('test Url Match Pattern', () => { - it('test UrlMatchPattern', () => { - const pattern = 'http+https+custom://*.insomnia.com:80/p1/*'; - const matchPattern = new UrlMatchPattern(pattern); + it('test UrlMatchPattern', () => { + const pattern = 'http+https+custom://*.insomnia.com:80/p1/*'; + const matchPattern = new UrlMatchPattern(pattern); - expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']); - expect(matchPattern.testProtocol('http')).toBeTruthy(); - expect(matchPattern.testProtocol('https')).toBeTruthy(); - expect(matchPattern.testProtocol('custom')).toBeTruthy(); - expect(matchPattern.testProtocol('unmatched')).toBeFalsy(); + expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']); + expect(matchPattern.testProtocol('http')).toBeTruthy(); + expect(matchPattern.testProtocol('https')).toBeTruthy(); + expect(matchPattern.testProtocol('custom')).toBeTruthy(); + expect(matchPattern.testProtocol('unmatched')).toBeFalsy(); - expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy(); - expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy(); - expect(matchPattern.testHost('insomnia.com')).toBeFalsy(); - expect(matchPattern.testHost('com')).toBeFalsy(); + expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy(); + expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy(); + expect(matchPattern.testHost('insomnia.com')).toBeFalsy(); + expect(matchPattern.testHost('com')).toBeFalsy(); - expect(matchPattern.testPath('/p1/abc')).toBeTruthy(); - expect(matchPattern.testPath('/p1/')).toBeTruthy(); - expect(matchPattern.testPath('/p1')).toBeFalsy(); - expect(matchPattern.testPath('/')).toBeFalsy(); - expect(matchPattern.testPath('')).toBeFalsy(); + expect(matchPattern.testPath('/p1/abc')).toBeTruthy(); + expect(matchPattern.testPath('/p1/')).toBeTruthy(); + expect(matchPattern.testPath('/p1')).toBeFalsy(); + expect(matchPattern.testPath('/')).toBeFalsy(); + expect(matchPattern.testPath('')).toBeFalsy(); - expect(matchPattern.testPort('80', 'https')).toBeTruthy(); - expect(matchPattern.testPort('443', 'https')).toBeFalsy(); - expect(matchPattern.testPort('80', 'http')).toBeTruthy(); - expect(matchPattern.testPort('80', 'unmatched')).toBeFalsy(); - }); + expect(matchPattern.testPort('80', 'https')).toBeTruthy(); + expect(matchPattern.testPort('443', 'https')).toBeFalsy(); + expect(matchPattern.testPort('80', 'http')).toBeTruthy(); + expect(matchPattern.testPort('80', 'unmatched')).toBeFalsy(); + }); - it('test UrlMatchPattern with no protocol', () => { - const pattern = '*.insomnia.com/p1/*'; - try { - const matchPattern = new UrlMatchPattern(pattern); - matchPattern.testProtocol('http'); - } catch (e: any) { - expect(e.message).toContain('UrlMatchPattern: protocol is not specified'); - } - }); + it('test UrlMatchPattern with no protocol', () => { + const pattern = '*.insomnia.com/p1/*'; + try { + const matchPattern = new UrlMatchPattern(pattern); + matchPattern.testProtocol('http'); + } catch (e: any) { + expect(e.message).toContain('UrlMatchPattern: protocol is not specified'); + } + }); - it('test UrlMatchPattern with no port', () => { - const pattern = 'http+https+custom://*.insomnia.com/p1/*'; - const matchPattern = new UrlMatchPattern(pattern); + it('test UrlMatchPattern with no port', () => { + const pattern = 'http+https+custom://*.insomnia.com/p1/*'; + const matchPattern = new UrlMatchPattern(pattern); - expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']); - expect(matchPattern.testProtocol('http')).toBeTruthy(); - expect(matchPattern.testProtocol('https')).toBeTruthy(); - expect(matchPattern.testProtocol('custom')).toBeTruthy(); - expect(matchPattern.testProtocol('unmatched')).toBeFalsy(); + expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']); + expect(matchPattern.testProtocol('http')).toBeTruthy(); + expect(matchPattern.testProtocol('https')).toBeTruthy(); + expect(matchPattern.testProtocol('custom')).toBeTruthy(); + expect(matchPattern.testProtocol('unmatched')).toBeFalsy(); - expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy(); - expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy(); - expect(matchPattern.testHost('insomnia.com')).toBeFalsy(); - expect(matchPattern.testHost('com')).toBeFalsy(); + expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy(); + expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy(); + expect(matchPattern.testHost('insomnia.com')).toBeFalsy(); + expect(matchPattern.testHost('com')).toBeFalsy(); - expect(matchPattern.testPath('/p1/abc')).toBeTruthy(); - expect(matchPattern.testPath('/p1/')).toBeTruthy(); - expect(matchPattern.testPath('/p1')).toBeFalsy(); - expect(matchPattern.testPath('/')).toBeFalsy(); - expect(matchPattern.testPath('')).toBeFalsy(); + expect(matchPattern.testPath('/p1/abc')).toBeTruthy(); + expect(matchPattern.testPath('/p1/')).toBeTruthy(); + expect(matchPattern.testPath('/p1')).toBeFalsy(); + expect(matchPattern.testPath('/')).toBeFalsy(); + expect(matchPattern.testPath('')).toBeFalsy(); - expect(matchPattern.testPort('443', 'https')).toBeTruthy(); - expect(matchPattern.testPort('80', 'http')).toBeTruthy(); - expect(matchPattern.testPort('443', 'http')).toBeFalsy(); - expect(matchPattern.testPort('80', 'https')).toBeFalsy(); - }); + expect(matchPattern.testPort('443', 'https')).toBeTruthy(); + expect(matchPattern.testPort('80', 'http')).toBeTruthy(); + expect(matchPattern.testPort('443', 'http')).toBeFalsy(); + expect(matchPattern.testPort('80', 'https')).toBeFalsy(); + }); - it('test UrlMatchPattern with no path', () => { - const pattern = 'http+https+custom://*.insomnia.com'; - const matchPattern = new UrlMatchPattern(pattern); + it('test UrlMatchPattern with no path', () => { + const pattern = 'http+https+custom://*.insomnia.com'; + const matchPattern = new UrlMatchPattern(pattern); - expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']); - expect(matchPattern.testProtocol('http')).toBeTruthy(); - expect(matchPattern.testProtocol('https')).toBeTruthy(); - expect(matchPattern.testProtocol('custom')).toBeTruthy(); - expect(matchPattern.testProtocol('unmatched')).toBeFalsy(); + expect(matchPattern.getProtocols()).toEqual(['http', 'https', 'custom']); + expect(matchPattern.testProtocol('http')).toBeTruthy(); + expect(matchPattern.testProtocol('https')).toBeTruthy(); + expect(matchPattern.testProtocol('custom')).toBeTruthy(); + expect(matchPattern.testProtocol('unmatched')).toBeFalsy(); - expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy(); - expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy(); - expect(matchPattern.testHost('insomnia.com')).toBeFalsy(); - expect(matchPattern.testHost('com')).toBeFalsy(); + expect(matchPattern.testHost('download.insomnia.com')).toBeTruthy(); + expect(matchPattern.testHost('bin.download.insomnia.com')).toBeFalsy(); + expect(matchPattern.testHost('insomnia.com')).toBeFalsy(); + expect(matchPattern.testHost('com')).toBeFalsy(); - expect(matchPattern.testPath('')).toBeTruthy(); - expect(matchPattern.testPath('/')).toBeFalsy(); // it is not handled temporarily + expect(matchPattern.testPath('')).toBeTruthy(); + expect(matchPattern.testPath('/')).toBeFalsy(); // it is not handled temporarily - expect(matchPattern.testPort('443', 'https')).toBeTruthy(); - expect(matchPattern.testPort('80', 'http')).toBeTruthy(); - expect(matchPattern.testPort('443', 'http')).toBeFalsy(); - expect(matchPattern.testPort('80', 'https')).toBeFalsy(); - }); + expect(matchPattern.testPort('443', 'https')).toBeTruthy(); + expect(matchPattern.testPort('80', 'http')).toBeTruthy(); + expect(matchPattern.testPort('443', 'http')).toBeFalsy(); + expect(matchPattern.testPort('80', 'https')).toBeFalsy(); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/__tests__/variables.test.ts b/packages/insomnia-scripting-environment/src/objects/__tests__/variables.test.ts index 608883e80b..c2cdac253d 100644 --- a/packages/insomnia-scripting-environment/src/objects/__tests__/variables.test.ts +++ b/packages/insomnia-scripting-environment/src/objects/__tests__/variables.test.ts @@ -3,34 +3,29 @@ import { describe, expect, it } from 'vitest'; import { Variable, VariableList } from '../variables'; describe('test Variables object', () => { - it('test basic operations', () => { - - const variable = new Variable({ - id: 'id', - key: 'key', - name: 'name', - value: 'value', - type: 'type', - disabled: false, - }); - - expect(variable.get()).toBe('value'); - variable.set('value2'); - expect(variable.get()).toBe('value2'); - + it('test basic operations', () => { + const variable = new Variable({ + id: 'id', + key: 'key', + name: 'name', + value: 'value', + type: 'type', + disabled: false, }); - it('VariableList operations', () => { - const varList = new VariableList( - undefined, - [ - new Variable({ key: 'h1', value: 'v1' }), - new Variable({ key: 'h2', value: 'v2' }), - ] - ); + expect(variable.get()).toBe('value'); + variable.set('value2'); + expect(variable.get()).toBe('value2'); + }); - const upserted = new Variable({ key: 'h1', value: 'v1upserted' }); - varList.upsert(upserted); - expect(varList.one('h1')).toEqual(upserted); - }); + it('VariableList operations', () => { + const varList = new VariableList(undefined, [ + new Variable({ key: 'h1', value: 'v1' }), + new Variable({ key: 'h2', value: 'v2' }), + ]); + + const upserted = new Variable({ key: 'h1', value: 'v1upserted' }); + varList.upsert(upserted); + expect(varList.one('h1')).toEqual(upserted); + }); }); diff --git a/packages/insomnia-scripting-environment/src/objects/async_objects.ts b/packages/insomnia-scripting-environment/src/objects/async_objects.ts index 3db0fd1b77..137a453348 100644 --- a/packages/insomnia-scripting-environment/src/objects/async_objects.ts +++ b/packages/insomnia-scripting-environment/src/objects/async_objects.ts @@ -4,78 +4,73 @@ let scriptPromises = new Array>(); export const OriginalPromise = Promise; export class ProxiedPromise extends Promise { - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - ) => void, - ) { - super(executor); - if (monitoring) { - scriptPromises.push(this); - } + constructor(executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void) => void) { + super(executor); + if (monitoring) { + scriptPromises.push(this); } + } - static override all(promises: Promise[]) { - const promise = super.all(promises); - if (monitoring) { - scriptPromises.push(promise); - } - return promise; + static override all(promises: Promise[]) { + const promise = super.all(promises); + if (monitoring) { + scriptPromises.push(promise); } + return promise; + } - static override allSettled(promises: Promise[]) { - // promise will be counted in Promise.resolve - return super.allSettled(promises); - } + static override allSettled(promises: Promise[]) { + // promise will be counted in Promise.resolve + return super.allSettled(promises); + } - // TODO: super.any seems not supported for the compile target (es2021) - - static any(_: Promise[]) { - return super.reject("'super.any' not supported"); - } + // TODO: super.any seems not supported for the compile target (es2021) - static override race(promises: Promise[]) { - const promise = super.race(promises); - if (monitoring) { - scriptPromises.push(promise); - } - return promise; - } + static any(_: Promise[]) { + return super.reject("'super.any' not supported"); + } - static override reject(value: any) { - const promise = super.reject(value); - if (monitoring) { - scriptPromises.push(promise); - } - return promise; + static override race(promises: Promise[]) { + const promise = super.race(promises); + if (monitoring) { + scriptPromises.push(promise); } + return promise; + } - static override resolve(value?: T | PromiseLike) { - const promise = super.resolve(value); - if (monitoring) { - scriptPromises.push(promise); - } - return promise; + static override reject(value: any) { + const promise = super.reject(value); + if (monitoring) { + scriptPromises.push(promise); } + return promise; + } - // TODO: Promise.any seems not supported for the compile target (es2021) - - static withResolvers() { - return super.reject("'Promise.withResolvers' not supported"); + static override resolve(value?: T | PromiseLike) { + const promise = super.resolve(value); + if (monitoring) { + scriptPromises.push(promise); } + return promise; + } + + // TODO: Promise.any seems not supported for the compile target (es2021) + + static withResolvers() { + return super.reject("'Promise.withResolvers' not supported"); + } } export const asyncTasksAllSettled = async () => { - await Promise.allSettled(scriptPromises); - scriptPromises = []; + await Promise.allSettled(scriptPromises); + scriptPromises = []; }; export const stopMonitorAsyncTasks = () => { - monitoring = false; + monitoring = false; }; export const resetAsyncTasks = async () => { - scriptPromises = []; - monitoring = true; + scriptPromises = []; + monitoring = true; }; diff --git a/packages/insomnia-scripting-environment/src/objects/auth.ts b/packages/insomnia-scripting-environment/src/objects/auth.ts index ac5148ca6c..93908636e5 100644 --- a/packages/insomnia-scripting-environment/src/objects/auth.ts +++ b/packages/insomnia-scripting-environment/src/objects/auth.ts @@ -5,744 +5,760 @@ import { Property } from './properties'; import { Variable, VariableList } from './variables'; export type AuthOptionTypes = - 'noauth' - | 'basic' - | 'bearer' - | 'jwt' - | 'digest' - | 'oauth1' - | 'oauth2' - | 'hawk' - | 'awsv4' - | 'ntlm' - | 'apikey' - | 'edgegrid' - | 'asap' - | 'netrc'; + | 'noauth' + | 'basic' + | 'bearer' + | 'jwt' + | 'digest' + | 'oauth1' + | 'oauth2' + | 'hawk' + | 'awsv4' + | 'ntlm' + | 'apikey' + | 'edgegrid' + | 'asap' + | 'netrc'; export const AuthTypes = new Set([ - 'noauth', - 'basic', - 'bearer', - 'jwt', - 'digest', - 'oauth1', - 'oauth2', - 'hawk', - 'awsv4', - 'ntlm', - 'apikey', - 'edgegrid', - 'asap', - 'netrc', + 'noauth', + 'basic', + 'bearer', + 'jwt', + 'digest', + 'oauth1', + 'oauth2', + 'hawk', + 'awsv4', + 'ntlm', + 'apikey', + 'edgegrid', + 'asap', + 'netrc', ]); export interface AuthOption { - key: string; - value: string; - type?: string; + key: string; + value: string; + type?: string; } export interface OAuth2AuthOption { - key: string; - value: string | OAuth2Param[]; - type?: string; -}; + key: string; + value: string | OAuth2Param[]; + type?: string; +} export interface BasicOptions { - password: string; - username: string; - id?: string; + password: string; + username: string; + id?: string; } export interface BearerOptions { - token: string; - id?: string; + token: string; + id?: string; } export interface JWTOptions { - secret: string; - algorithm: string; - isSecretBase64Encoded: boolean; - payload: string; // e.g. "{}" - addTokenTo: string; - headerPrefix: string; - queryParamKey: string; - header: string; // e.g. "{}" - id?: string; + secret: string; + algorithm: string; + isSecretBase64Encoded: boolean; + payload: string; // e.g. "{}" + addTokenTo: string; + headerPrefix: string; + queryParamKey: string; + header: string; // e.g. "{}" + id?: string; } export interface DigestOptions { - opaque: string; - clientNonce: string; - nonceCount: string; - qop: string; - nonce: string; - realm: string; - password: string; - username: string; - algorithm: string; - id?: string; + opaque: string; + clientNonce: string; + nonceCount: string; + qop: string; + nonce: string; + realm: string; + password: string; + username: string; + algorithm: string; + id?: string; } export interface OAuth1Options { - addEmptyParamsToSign: boolean; - includeBodyHash: boolean; - realm: string; - nonce: string; - timestamp: string; - verifier: string; - callback: string; - tokenSecret: string; - token: string; - consumerSecret: string; - consumerKey: string; - signatureMethod: string; // "HMAC-SHA1" - version: string; - addParamsToHeader: string; - id?: string; + addEmptyParamsToSign: boolean; + includeBodyHash: boolean; + realm: string; + nonce: string; + timestamp: string; + verifier: string; + callback: string; + tokenSecret: string; + token: string; + consumerSecret: string; + consumerKey: string; + signatureMethod: string; // "HMAC-SHA1" + version: string; + addParamsToHeader: string; + id?: string; } export interface OAuth2Param { - key: string; - value: string; - enabled: boolean; - send_as: string; // it follows existing naming + key: string; + value: string; + enabled: boolean; + send_as: string; // it follows existing naming } export interface OAuth2Options { - accessToken: string; - refreshRequestParams: OAuth2Param[]; - tokenRequestParams: OAuth2Param[]; - authRequestParams: OAuth2Param[]; - refreshTokenUrl: string; - state: string; - scope: string; - clientSecret: string; - clientId: string; - accessTokenUrl: string; - authUrl: string; - tokenName: string; - addTokenTo: string; - code_verifier: string; - id?: string; + accessToken: string; + refreshRequestParams: OAuth2Param[]; + tokenRequestParams: OAuth2Param[]; + authRequestParams: OAuth2Param[]; + refreshTokenUrl: string; + state: string; + scope: string; + clientSecret: string; + clientId: string; + accessTokenUrl: string; + authUrl: string; + tokenName: string; + addTokenTo: string; + code_verifier: string; + id?: string; } export interface HAWKOptions { - includePayloadHash: boolean; - timestamp: string; - delegation: string; - app: string; - extraData: string; - nonce: string; - user: string; - authKey: string; - authId: string; - algorithm: string; - id?: string; + includePayloadHash: boolean; + timestamp: string; + delegation: string; + app: string; + extraData: string; + nonce: string; + user: string; + authKey: string; + authId: string; + algorithm: string; + id?: string; } export interface AWSV4Options { - sessionToken: string; - service: string; - region: string; - secretKey: string; - accessKey: string; - id?: string; + sessionToken: string; + service: string; + region: string; + secretKey: string; + accessKey: string; + id?: string; } export interface NTLMOptions { - workstation: string; - domain: string; - password: string; - username: string; - id?: string; + workstation: string; + domain: string; + password: string; + username: string; + id?: string; } export interface APIKeyOptions { - key: string; - value: string; - id?: string; + key: string; + value: string; + id?: string; } export interface EdgegridOptions { - headersToSign: string; - baseURL: string; - timestamp: string; - nonce: string; - clientSecret: string; - clientToken: string; - accessToken: string; - id?: string; + headersToSign: string; + baseURL: string; + timestamp: string; + nonce: string; + clientSecret: string; + clientToken: string; + accessToken: string; + id?: string; } export interface ASAPOptions { - exp: string; // expiry - claims: string; // e.g., { "additional claim": "claim value" } - sub: string; // subject - privateKey: string; // private key - kid: string; // key id - aud: string; // audience - iss: string; // issuer - alg: string; // e.g., RS256 - id?: string; + exp: string; // expiry + claims: string; // e.g., { "additional claim": "claim value" } + sub: string; // subject + privateKey: string; // private key + kid: string; // key id + aud: string; // audience + iss: string; // issuer + alg: string; // e.g., RS256 + id?: string; } export function authOptionsToParams( - authMethod: BasicOptions | BearerOptions | JWTOptions | DigestOptions | OAuth1Options | OAuth2Options | HAWKOptions | AWSV4Options | NTLMOptions | APIKeyOptions | EdgegridOptions | ASAPOptions + authMethod: + | BasicOptions + | BearerOptions + | JWTOptions + | DigestOptions + | OAuth1Options + | OAuth2Options + | HAWKOptions + | AWSV4Options + | NTLMOptions + | APIKeyOptions + | EdgegridOptions + | ASAPOptions, ) { - return Object.entries(authMethod). - map(entry => ({ - type: 'any', - key: entry[0], - value: entry[1], - })); + return Object.entries(authMethod).map(entry => ({ + type: 'any', + key: entry[0], + value: entry[1], + })); } export interface AuthOptions { - type: AuthOptionTypes; - basic?: AuthOption[]; - bearer?: AuthOption[]; - jwt?: AuthOption[]; - digest?: AuthOption[]; - oauth1?: AuthOption[]; - oauth2?: OAuth2AuthOption[]; - hawk?: AuthOption[]; - awsv4?: AuthOption[]; - ntlm?: AuthOption[]; - apikey?: AuthOption[]; - edgegrid?: AuthOption[]; - asap?: AuthOption[]; + type: AuthOptionTypes; + basic?: AuthOption[]; + bearer?: AuthOption[]; + jwt?: AuthOption[]; + digest?: AuthOption[]; + oauth1?: AuthOption[]; + oauth2?: OAuth2AuthOption[]; + hawk?: AuthOption[]; + awsv4?: AuthOption[]; + ntlm?: AuthOption[]; + apikey?: AuthOption[]; + edgegrid?: AuthOption[]; + asap?: AuthOption[]; } -function rawOptionsToVariables(options: VariableList | Variable[] | AuthOptions, targetType?: string): VariableList[] { - if (VariableList.isVariableList(options)) { - return [options as VariableList]; - } else if ('type' in options) { - // options is AuthOptions - const optsObj = options as AuthOptions; - const optsVarLists = Object.entries(optsObj) - .filter(optsObjEntry => { - return optsObjEntry[0] === targetType; - }) - .map(optsEntry => { - const optVars = optsEntry[1].map((opt: AuthOption) => { - return new Variable({ - key: opt.key, - value: opt.value, - }); - }); - return new VariableList(undefined, optVars); - }); +function rawOptionsToVariables( + options: VariableList | Variable[] | AuthOptions, + targetType?: string, +): VariableList[] { + if (VariableList.isVariableList(options)) { + return [options as VariableList]; + } else if ('type' in options) { + // options is AuthOptions + const optsObj = options as AuthOptions; + const optsVarLists = Object.entries(optsObj) + .filter(optsObjEntry => { + return optsObjEntry[0] === targetType; + }) + .map(optsEntry => { + const optVars = optsEntry[1].map((opt: AuthOption) => { + return new Variable({ + key: opt.key, + value: opt.value, + }); + }); + return new VariableList(undefined, optVars); + }); - return optsVarLists; - } else if ('length' in options) { // array - return [new VariableList(undefined, options)]; - } + return optsVarLists; + } else if ('length' in options) { + // array + return [new VariableList(undefined, options)]; + } - throw Error('options is not valid: it must be VariableList | Variable[] | object'); + throw Error('options is not valid: it must be VariableList | Variable[] | object'); } export class RequestAuth extends Property { - private type: AuthOptionTypes; - private authOptions = new Map>(); + private type: AuthOptionTypes; + private authOptions = new Map>(); - constructor(options: AuthOptions, parent?: Property) { - super(); + constructor(options: AuthOptions, parent?: Property) { + super(); - if (!RequestAuth.isValidType(options.type)) { - throw Error(`invalid auth type ${options.type}`); - } - - this.type = options.type; - const optsObj = options as AuthOptions; - const optsEntries = Object.entries(optsObj) - .filter(optsObjEntry => optsObjEntry[0] !== 'type'); - - optsEntries.map((optsEntry: [string, AuthOption[]]) => { - const optVars = optsEntry[1] - .map(opt => { - return new Variable({ - key: opt.key, - value: opt.value, - type: opt.type, - }); - }); - - return { - type: optsEntry[0], - options: new VariableList(undefined, optVars), - }; - }) - .forEach(authOpts => { - this.authOptions.set(authOpts.type, authOpts.options); - }); - - this._parent = parent; + if (!RequestAuth.isValidType(options.type)) { + throw Error(`invalid auth type ${options.type}`); } - static isValidType(authType: string) { - return AuthTypes.has(authType); + this.type = options.type; + const optsObj = options as AuthOptions; + const optsEntries = Object.entries(optsObj).filter(optsObjEntry => optsObjEntry[0] !== 'type'); + + optsEntries + .map((optsEntry: [string, AuthOption[]]) => { + const optVars = optsEntry[1].map(opt => { + return new Variable({ + key: opt.key, + value: opt.value, + type: opt.type, + }); + }); + + return { + type: optsEntry[0], + options: new VariableList(undefined, optVars), + }; + }) + .forEach(authOpts => { + this.authOptions.set(authOpts.type, authOpts.options); + }); + + this._parent = parent; + } + + static isValidType(authType: string) { + return AuthTypes.has(authType); + } + + clear(type: string) { + if (RequestAuth.isValidType(type)) { + this.authOptions.delete(type); + } + } + + parameters(): VariableList | undefined { + return this.authOptions.get(this.type); + } + + override toJSON() { + const obj: AuthOptions = { type: this.type }; + const authOption = this.authOptions.get(this.type); + if (!authOption) { + return obj; } - clear(type: string) { - if (RequestAuth.isValidType(type)) { - this.authOptions.delete(type); - } + if (this.type === 'noauth' || this.type === 'netrc') { + return obj; + } + obj[this.type] = authOption.map(optValue => optValue.toJSON(), {}); + return obj; + } + + update(options: VariableList | Variable[] | AuthOptions, type?: AuthOptionTypes) { + const currentType = type ? type : this.type; + const authOpts = rawOptionsToVariables(options, currentType); + + if (authOpts.length > 0 && authOpts[0]) { + this.type = currentType; + this.authOptions.set(currentType, authOpts[0]); + } else { + throw Error('no valid RequestAuth options is found'); + } + } + + use(type: AuthOptionTypes, options: VariableList | Variable[] | AuthOptions) { + if (!RequestAuth.isValidType(type)) { + throw Error( + `invalid type (${type}), it must be noauth | basic | bearer | jwt | digest | oauth1 | oauth2 | hawk | awsv4 | ntlm | apikey | edgegrid | asap.`, + ); } - parameters(): VariableList | undefined { - return this.authOptions.get(this.type); - } - - override toJSON() { - const obj: AuthOptions = { type: this.type }; - const authOption = this.authOptions.get(this.type); - if (!authOption) { - return obj; - } - - if (this.type === 'noauth' || this.type === 'netrc') { - return obj; - } - obj[this.type] = authOption.map(optValue => optValue.toJSON(), {}); - return obj; - } - - update(options: VariableList | Variable[] | AuthOptions, type?: AuthOptionTypes) { - const currentType = type ? type : this.type; - const authOpts = rawOptionsToVariables(options, currentType); - - if (authOpts.length > 0 && authOpts[0]) { - this.type = currentType; - this.authOptions.set(currentType, authOpts[0]); - } else { - throw Error('no valid RequestAuth options is found'); - } - } - - use(type: AuthOptionTypes, options: VariableList | Variable[] | AuthOptions) { - if (!RequestAuth.isValidType(type)) { - throw Error(`invalid type (${type}), it must be noauth | basic | bearer | jwt | digest | oauth1 | oauth2 | hawk | awsv4 | ntlm | apikey | edgegrid | asap.`); - } - - const authOpts = rawOptionsToVariables(options, type); - if (authOpts.length > 0 && authOpts[0]) { - this.type = type; - this.authOptions.set(type, authOpts[0]); - } else { - throw Error('no valid RequestAuth options is found'); - } + const authOpts = rawOptionsToVariables(options, type); + if (authOpts.length > 0 && authOpts[0]) { + this.type = type; + this.authOptions.set(type, authOpts[0]); + } else { + throw Error('no valid RequestAuth options is found'); } + } } export function fromPreRequestAuth(auth: RequestAuth): RequestAuthentication { - const authObj = auth.toJSON(); + const authObj = auth.toJSON(); - const findValueInKvArray = (targetKey: string, kvs?: { key: string; value: string }[]) => - kvs?.find(({ key }) => key === targetKey)?.value || ''; + const findValueInKvArray = (targetKey: string, kvs?: { key: string; value: string }[]) => + kvs?.find(({ key }) => key === targetKey)?.value || ''; - const findValueInOauth2Options = ( - targetKey: string, - kvs?: { key: string; value: string }[] | OAuth2AuthOption[] - ) => { - if (!kvs) { - return ''; - } - - for (const kv of kvs) { - if (typeof kv.value === 'string' && kv.key === targetKey) { - return kv.value; - } else if (Array.isArray(kv.value)) { - const matched = kv.value.find(subKv => subKv.key === targetKey); - if (matched != null) { - return matched.value; - } - } - } - - return ''; - }; - - switch (authObj.type) { - case 'noauth': - return { - type: 'none', - disabled: true, - }; - case 'apikey': - return { - type: 'apikey', - disabled: findValueInKvArray('disabled', authObj.apikey) === 'true', - key: findValueInKvArray('key', authObj.apikey), - value: findValueInKvArray('value', authObj.apikey), - addTo: findValueInKvArray('in', authObj.apikey), - }; - case 'bearer': - return { - type: 'bearer', - disabled: findValueInKvArray('disabled', authObj.bearer) === 'true', - token: findValueInKvArray('token', authObj.bearer), - prefix: findValueInKvArray('prefix', authObj.bearer), - }; - case 'basic': - return { - type: 'basic', - disabled: findValueInKvArray('disabled', authObj.basic) === 'true', - useISO88591: findValueInKvArray('useISO88591', authObj.basic) === 'true', - username: findValueInKvArray('username', authObj.basic), - password: findValueInKvArray('password', authObj.basic), - }; - case 'digest': - return { - type: 'digest', - disabled: findValueInKvArray('disabled', authObj.digest) === 'true', - username: findValueInKvArray('username', authObj.digest), - password: findValueInKvArray('password', authObj.digest), - }; - case 'ntlm': - return { - type: 'ntlm', - disabled: findValueInKvArray('disabled', authObj.ntlm) === 'true', - username: findValueInKvArray('username', authObj.ntlm), - password: findValueInKvArray('password', authObj.ntlm), - }; - case 'oauth1': { - const signMethod = ((): OAuth1SignatureMethod => { - const method = findValueInKvArray('signatureMethod', authObj.oauth1); - const unsupportedError = Error(`auth transforming(fromPreRequestAuth): unsupported signatureMethod type for oauth1: ${method}`); - switch (method) { - case 'HMAC-SHA1': - return 'HMAC-SHA1'; - case 'HMAC-SHA256': - return 'HMAC-SHA256'; - case 'HMAC-SHA512': - throw unsupportedError; - case 'RSA-SHA1': - return 'RSA-SHA1'; - case 'RSA-SHA256': - throw unsupportedError; - case 'RSA-SHA512': - throw unsupportedError; - case 'PLAINTEXT': - return 'PLAINTEXT'; - default: - throw Error(`auth transforming(fromPreRequestAuth): unknown signatureMethod type for oauth1: ${method}`); - } - })(); - - return { - type: 'oauth1', - disabled: findValueInKvArray('disabled', authObj.oauth1) === 'true', - signatureMethod: signMethod, - consumerKey: findValueInKvArray('consumerKey', authObj.oauth1), - consumerSecret: findValueInKvArray('consumerSecret', authObj.oauth1), - tokenKey: findValueInKvArray('token', authObj.oauth1), - tokenSecret: findValueInKvArray('tokenSecret', authObj.oauth1), - privateKey: findValueInKvArray('privateKey', authObj.oauth1), // it is not supported in the script side - version: findValueInKvArray('version', authObj.oauth1), - nonce: findValueInKvArray('nonce', authObj.oauth1), - timestamp: findValueInKvArray('timestamp', authObj.oauth1), - callback: findValueInKvArray('callback', authObj.oauth1), - realm: findValueInKvArray('realm', authObj.oauth1), - verifier: findValueInKvArray('verifier', authObj.oauth1), - includeBodyHash: findValueInKvArray('includeBodyHash', authObj.oauth1) === 'true', - }; - }; - case 'oauth2': { - const inputGrantType = findValueInOauth2Options('grant_type', authObj.oauth2); - const grantType = (() => { - switch (inputGrantType) { - case 'authorization_code': - case 'authorization_code_with_pkce': - return 'authorization_code'; - case 'implicit': - return 'implicit'; - case 'password_credentials': - return 'password'; - case 'client_credentials': - return 'client_credentials'; - case 'refresh_token': - return 'refresh_token'; - default: - throw Error(`auth transforming(fromPreRequestAuth): unknown auth grant type for oauth2: ${inputGrantType}`); - } - })(); - - const responseType = ((): OAuth2ResponseType => { - const inputResponseType = findValueInOauth2Options('response_type', authObj.oauth2); - // responseType is currently always set to '' in our request auth model, this should be investigated what is correct to be set - if (['code', 'id_token', 'id_token token', 'none', 'token', ''].includes(inputResponseType)) { - return inputResponseType as OAuth2ResponseType; - }; - throw Error(`unknown response type for oauth2: "${inputResponseType}", it could be: 'code' | 'id_token' | 'id_token token' | 'none' | 'token' | ''`); - })(); - - return { - type: 'oauth2', - disabled: findValueInOauth2Options('disabled', authObj.oauth2) === 'true', - grantType: grantType, - authorizationUrl: findValueInOauth2Options('authUrl', authObj.oauth2), - accessTokenUrl: findValueInOauth2Options('accessTokenUrl', authObj.oauth2), - clientId: findValueInOauth2Options('clientId', authObj.oauth2), - clientSecret: findValueInOauth2Options('clientSecret', authObj.oauth2), - scope: findValueInOauth2Options('scope', authObj.oauth2), - code: findValueInOauth2Options('code_verifier', authObj.oauth2), - accessToken: findValueInOauth2Options('accessToken', authObj.oauth2), - pkceMethod: findValueInOauth2Options('challengeAlgorithm', authObj.oauth2), - usePkce: findValueInOauth2Options('grant_type', authObj.oauth2) === 'authorization_code_with_pkce', - username: findValueInOauth2Options('username', authObj.oauth2), - password: findValueInOauth2Options('password', authObj.oauth2), - redirectUrl: findValueInOauth2Options('callBackUrl', authObj.oauth2), - state: findValueInOauth2Options('state', authObj.oauth2), - refreshToken: findValueInOauth2Options('refreshTokenUrl', authObj.oauth2), - credentialsInBody: findValueInOauth2Options('client_authentication', authObj.oauth2) === 'body', - audience: findValueInOauth2Options('audience', authObj.oauth2) || '', - resource: findValueInOauth2Options('resource', authObj.oauth2) || '', - // following properties are not supported yet in the script side, just try to find and set them - tokenPrefix: findValueInOauth2Options('tokenPrefix', authObj.oauth2), - responseType: responseType, - origin: findValueInOauth2Options('origin', authObj.oauth2), - }; - }; - case 'awsv4': - return { - type: 'iam', - disabled: findValueInKvArray('disabled', authObj.awsv4) === 'true', - accessKeyId: findValueInKvArray('accessKey', authObj.awsv4), - secretAccessKey: findValueInKvArray('secretKey', authObj.awsv4), - sessionToken: findValueInKvArray('sessionToken', authObj.awsv4), - region: findValueInKvArray('region', authObj.awsv4), - service: findValueInKvArray('service', authObj.awsv4), - }; - case 'hawk': - return { - type: 'hawk', - disabled: findValueInKvArray('disabled', authObj.hawk) === 'true', - algorithm: findValueInKvArray('algorithm', authObj.hawk) === 'sha256' ? 'sha256' : 'sha1', - id: findValueInKvArray('authId', authObj.hawk), - key: findValueInKvArray('authKey', authObj.hawk), - ext: findValueInKvArray('extraData', authObj.hawk), - validatePayload: findValueInKvArray('validatePayload', authObj.hawk) === 'true', - // TODO(george): some keys are lost here, see if we can support them in Insomnia - // timestamp - // delegation - // app - // nonce - // user - }; - case 'asap': - return { - type: 'asap', - disabled: findValueInKvArray('disabled', authObj.asap) === 'true', - issuer: findValueInKvArray('iss', authObj.asap), - subject: findValueInKvArray('sub', authObj.asap), - audience: findValueInKvArray('aud', authObj.asap), - additionalClaims: findValueInKvArray('claims', authObj.asap), - keyId: findValueInKvArray('kid', authObj.asap), - privateKey: findValueInKvArray('privateKey', authObj.asap), - }; - case 'netrc': - // TODO(george): support this in the script side - throw Error('netrc is not supported yet'); - default: - throw Error(`unknown auth type: ${authObj.type}`); + const findValueInOauth2Options = (targetKey: string, kvs?: { key: string; value: string }[] | OAuth2AuthOption[]) => { + if (!kvs) { + return ''; } + + for (const kv of kvs) { + if (typeof kv.value === 'string' && kv.key === targetKey) { + return kv.value; + } else if (Array.isArray(kv.value)) { + const matched = kv.value.find(subKv => subKv.key === targetKey); + if (matched != null) { + return matched.value; + } + } + } + + return ''; + }; + + switch (authObj.type) { + case 'noauth': + return { + type: 'none', + disabled: true, + }; + case 'apikey': + return { + type: 'apikey', + disabled: findValueInKvArray('disabled', authObj.apikey) === 'true', + key: findValueInKvArray('key', authObj.apikey), + value: findValueInKvArray('value', authObj.apikey), + addTo: findValueInKvArray('in', authObj.apikey), + }; + case 'bearer': + return { + type: 'bearer', + disabled: findValueInKvArray('disabled', authObj.bearer) === 'true', + token: findValueInKvArray('token', authObj.bearer), + prefix: findValueInKvArray('prefix', authObj.bearer), + }; + case 'basic': + return { + type: 'basic', + disabled: findValueInKvArray('disabled', authObj.basic) === 'true', + useISO88591: findValueInKvArray('useISO88591', authObj.basic) === 'true', + username: findValueInKvArray('username', authObj.basic), + password: findValueInKvArray('password', authObj.basic), + }; + case 'digest': + return { + type: 'digest', + disabled: findValueInKvArray('disabled', authObj.digest) === 'true', + username: findValueInKvArray('username', authObj.digest), + password: findValueInKvArray('password', authObj.digest), + }; + case 'ntlm': + return { + type: 'ntlm', + disabled: findValueInKvArray('disabled', authObj.ntlm) === 'true', + username: findValueInKvArray('username', authObj.ntlm), + password: findValueInKvArray('password', authObj.ntlm), + }; + case 'oauth1': { + const signMethod = ((): OAuth1SignatureMethod => { + const method = findValueInKvArray('signatureMethod', authObj.oauth1); + const unsupportedError = Error( + `auth transforming(fromPreRequestAuth): unsupported signatureMethod type for oauth1: ${method}`, + ); + switch (method) { + case 'HMAC-SHA1': + return 'HMAC-SHA1'; + case 'HMAC-SHA256': + return 'HMAC-SHA256'; + case 'HMAC-SHA512': + throw unsupportedError; + case 'RSA-SHA1': + return 'RSA-SHA1'; + case 'RSA-SHA256': + throw unsupportedError; + case 'RSA-SHA512': + throw unsupportedError; + case 'PLAINTEXT': + return 'PLAINTEXT'; + default: + throw Error(`auth transforming(fromPreRequestAuth): unknown signatureMethod type for oauth1: ${method}`); + } + })(); + + return { + type: 'oauth1', + disabled: findValueInKvArray('disabled', authObj.oauth1) === 'true', + signatureMethod: signMethod, + consumerKey: findValueInKvArray('consumerKey', authObj.oauth1), + consumerSecret: findValueInKvArray('consumerSecret', authObj.oauth1), + tokenKey: findValueInKvArray('token', authObj.oauth1), + tokenSecret: findValueInKvArray('tokenSecret', authObj.oauth1), + privateKey: findValueInKvArray('privateKey', authObj.oauth1), // it is not supported in the script side + version: findValueInKvArray('version', authObj.oauth1), + nonce: findValueInKvArray('nonce', authObj.oauth1), + timestamp: findValueInKvArray('timestamp', authObj.oauth1), + callback: findValueInKvArray('callback', authObj.oauth1), + realm: findValueInKvArray('realm', authObj.oauth1), + verifier: findValueInKvArray('verifier', authObj.oauth1), + includeBodyHash: findValueInKvArray('includeBodyHash', authObj.oauth1) === 'true', + }; + } + case 'oauth2': { + const inputGrantType = findValueInOauth2Options('grant_type', authObj.oauth2); + const grantType = (() => { + switch (inputGrantType) { + case 'authorization_code': + case 'authorization_code_with_pkce': + return 'authorization_code'; + case 'implicit': + return 'implicit'; + case 'password_credentials': + return 'password'; + case 'client_credentials': + return 'client_credentials'; + case 'refresh_token': + return 'refresh_token'; + default: + throw Error(`auth transforming(fromPreRequestAuth): unknown auth grant type for oauth2: ${inputGrantType}`); + } + })(); + + const responseType = ((): OAuth2ResponseType => { + const inputResponseType = findValueInOauth2Options('response_type', authObj.oauth2); + // responseType is currently always set to '' in our request auth model, this should be investigated what is correct to be set + if (['code', 'id_token', 'id_token token', 'none', 'token', ''].includes(inputResponseType)) { + return inputResponseType as OAuth2ResponseType; + } + throw Error( + `unknown response type for oauth2: "${inputResponseType}", it could be: 'code' | 'id_token' | 'id_token token' | 'none' | 'token' | ''`, + ); + })(); + + return { + type: 'oauth2', + disabled: findValueInOauth2Options('disabled', authObj.oauth2) === 'true', + grantType: grantType, + authorizationUrl: findValueInOauth2Options('authUrl', authObj.oauth2), + accessTokenUrl: findValueInOauth2Options('accessTokenUrl', authObj.oauth2), + clientId: findValueInOauth2Options('clientId', authObj.oauth2), + clientSecret: findValueInOauth2Options('clientSecret', authObj.oauth2), + scope: findValueInOauth2Options('scope', authObj.oauth2), + code: findValueInOauth2Options('code_verifier', authObj.oauth2), + accessToken: findValueInOauth2Options('accessToken', authObj.oauth2), + pkceMethod: findValueInOauth2Options('challengeAlgorithm', authObj.oauth2), + usePkce: findValueInOauth2Options('grant_type', authObj.oauth2) === 'authorization_code_with_pkce', + username: findValueInOauth2Options('username', authObj.oauth2), + password: findValueInOauth2Options('password', authObj.oauth2), + redirectUrl: findValueInOauth2Options('callBackUrl', authObj.oauth2), + state: findValueInOauth2Options('state', authObj.oauth2), + refreshToken: findValueInOauth2Options('refreshTokenUrl', authObj.oauth2), + credentialsInBody: findValueInOauth2Options('client_authentication', authObj.oauth2) === 'body', + audience: findValueInOauth2Options('audience', authObj.oauth2) || '', + resource: findValueInOauth2Options('resource', authObj.oauth2) || '', + // following properties are not supported yet in the script side, just try to find and set them + tokenPrefix: findValueInOauth2Options('tokenPrefix', authObj.oauth2), + responseType: responseType, + origin: findValueInOauth2Options('origin', authObj.oauth2), + }; + } + case 'awsv4': + return { + type: 'iam', + disabled: findValueInKvArray('disabled', authObj.awsv4) === 'true', + accessKeyId: findValueInKvArray('accessKey', authObj.awsv4), + secretAccessKey: findValueInKvArray('secretKey', authObj.awsv4), + sessionToken: findValueInKvArray('sessionToken', authObj.awsv4), + region: findValueInKvArray('region', authObj.awsv4), + service: findValueInKvArray('service', authObj.awsv4), + }; + case 'hawk': + return { + type: 'hawk', + disabled: findValueInKvArray('disabled', authObj.hawk) === 'true', + algorithm: findValueInKvArray('algorithm', authObj.hawk) === 'sha256' ? 'sha256' : 'sha1', + id: findValueInKvArray('authId', authObj.hawk), + key: findValueInKvArray('authKey', authObj.hawk), + ext: findValueInKvArray('extraData', authObj.hawk), + validatePayload: findValueInKvArray('validatePayload', authObj.hawk) === 'true', + // TODO(george): some keys are lost here, see if we can support them in Insomnia + // timestamp + // delegation + // app + // nonce + // user + }; + case 'asap': + return { + type: 'asap', + disabled: findValueInKvArray('disabled', authObj.asap) === 'true', + issuer: findValueInKvArray('iss', authObj.asap), + subject: findValueInKvArray('sub', authObj.asap), + audience: findValueInKvArray('aud', authObj.asap), + additionalClaims: findValueInKvArray('claims', authObj.asap), + keyId: findValueInKvArray('kid', authObj.asap), + privateKey: findValueInKvArray('privateKey', authObj.asap), + }; + case 'netrc': + // TODO(george): support this in the script side + throw Error('netrc is not supported yet'); + default: + throw Error(`unknown auth type: ${authObj.type}`); + } } export function toPreRequestAuth(auth: RequestAuthentication | {}): AuthOptions { - if (!auth || !('type' in auth) || !auth.type) { - return { type: 'noauth' }; - } - - switch (auth.type) { - case 'none': - return { type: 'noauth' }; - case 'apikey': - return { - type: 'apikey', - apikey: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'key', value: auth.key || '' }, - { key: 'value', value: auth.value || '' }, - { key: 'in', value: auth.addTo || '' }, - ], - }; - case 'bearer': - return { - type: 'bearer', - bearer: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'token', value: auth.token || '' }, - { key: 'prefix', value: auth.prefix || '' }, - ], - }; - case 'basic': - return { - type: 'basic', - basic: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'useISO88591', value: auth.useISO88591 ? 'true' : 'false' }, - { key: 'username', value: auth.username || '' }, - { key: 'password', value: auth.password || '' }, - ], - }; - case 'digest': - return { - type: 'digest', - digest: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'username', value: auth.username || '' }, - { key: 'password', value: auth.password || '' }, - ], - }; - case 'ntlm': - return { - type: 'ntlm', - ntlm: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'username', value: auth.username || '' }, - { key: 'password', value: auth.password || '' }, - ], - }; - case 'oauth1': - return { - type: 'oauth1', - oauth1: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'signatureMethod', value: auth.signatureMethod || '' }, - { key: 'consumerKey', value: auth.consumerKey || '' }, - { key: 'consumerSecret', value: auth.consumerSecret || '' }, - { key: 'token', value: auth.tokenKey || '' }, - - { key: 'tokenSecret', value: auth.tokenSecret || '' }, - { key: 'privateKey', value: auth.privateKey || '' }, - { key: 'version', value: auth.version || '' }, - { key: 'nonce', value: auth.nonce || '' }, - { key: 'timestamp', value: auth.timestamp || '' }, - - { key: 'callback', value: auth.callback || '' }, - { key: 'realm', value: auth.realm || '' }, - { key: 'verifier', value: auth.verifier || '' }, - { key: 'includeBodyHash', value: auth.includeBodyHash ? 'true' : 'false' }, - ], - }; - case 'oauth2': { - const inputGrantType = auth.grantType; - const grantType = (() => { - switch (inputGrantType) { - case 'authorization_code': - return auth.usePkce ? 'authorization_code_with_pkce' : 'authorization_code'; - case 'implicit': - return 'implicit'; - case 'password': - return 'password_credentials'; - case 'client_credentials': - return 'client_credentials'; - case 'refresh_token': - return 'refresh_token'; - default: - throw Error(`auth transforming(toPreRequestAuth): unknown auth grant type for oauth2: ${inputGrantType}`); - } - })(); - - return { - type: 'oauth2', - oauth2: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - - { key: 'grant_type', value: grantType }, - { key: 'authUrl', value: auth.authorizationUrl || '' }, - { key: 'accessTokenUrl', value: auth.accessTokenUrl || '' }, - { key: 'clientId', value: auth.clientId || '' }, - { key: 'clientSecret', value: auth.clientSecret || '' }, - { key: 'scope', value: auth.scope || '' }, - - { key: 'code_verifier', value: auth.code || '' }, - { key: 'accessToken', value: auth.accessToken || '' }, - { key: 'challengeAlgorithm', value: auth.pkceMethod || '' }, - // { key: 'scope', value: auth.usePkce || ''}, - { key: 'username', value: auth.username || '' }, - { key: 'password', value: auth.password || '' }, - - { key: 'callBackUrl', value: auth.redirectUrl || '' }, - { key: 'state', value: auth.state || '' }, - { key: 'refreshTokenUrl', value: auth.refreshToken || '' }, - { key: 'client_authentication', value: auth.credentialsInBody ? 'body' : 'header' }, - - { - key: 'tokenRequestParams', value: [ - { - key: 'audience', - value: auth.audience || '', - enabled: true, - send_as: 'request_url', // request_body or request_header - - }, - { - key: 'resource', - value: auth.resource || '', - enabled: true, - send_as: 'request_url', // request_body or request_header - - }, - ], - }, - - // following properties are not supported in script side, still set them - { key: 'tokenPrefix', value: auth.tokenPrefix || '' }, - { key: 'response_type', value: auth.responseType || '' }, - { key: 'origin', value: auth.origin || '' }, - ], - }; - }; - case 'iam': - return { - type: 'awsv4', - awsv4: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'accessKey', value: auth.accessKeyId || '' }, - { key: 'secretKey', value: auth.secretAccessKey || '' }, - { key: 'sessionToken', value: auth.sessionToken || '' }, - { key: 'region', value: auth.region || '' }, - { key: 'service', value: auth.service || '' }, - ], - }; - case 'hawk': - return { - type: 'hawk', - hawk: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'algorithm', value: auth.algorithm || '' }, - { key: 'authId', value: auth.id || '' }, - { key: 'authKey', value: auth.key || '' }, - { key: 'extraData', value: auth.ext || '' }, - { key: 'validatePayload', value: auth.validatePayload ? 'true' : 'false' }, - // TODO(george): these fields are not supported in Insomnia side - { key: 'timestamp', value: '' }, - { key: 'delegation', value: '' }, - { key: 'app', value: '' }, - { key: 'nonce', value: '' }, - { key: 'user', value: '' }, - { key: 'includePayloadHash', value: 'false' }, - ], - }; - case 'asap': - return { - type: 'asap', - asap: [ - { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, - { key: 'iss', value: auth.issuer || '' }, - { key: 'sub', value: auth.subject || '' }, - { key: 'aud', value: auth.audience || '' }, - { key: 'claims', value: auth.additionalClaims || '' }, - { key: 'kid', value: auth.keyId || '' }, - { key: 'privateKey', value: auth.privateKey || '' }, - ], - }; - case 'netrc': - // TODO: not supported yet - throw Error('netrc auth is not supported in scripting yet'); - default: - // @ts-expect-error - user can input any string - throw Error(`unknown auth type: ${auth.type}`); + if (!auth || !('type' in auth) || !auth.type) { + return { type: 'noauth' }; + } + + switch (auth.type) { + case 'none': + return { type: 'noauth' }; + case 'apikey': + return { + type: 'apikey', + apikey: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'key', value: auth.key || '' }, + { key: 'value', value: auth.value || '' }, + { key: 'in', value: auth.addTo || '' }, + ], + }; + case 'bearer': + return { + type: 'bearer', + bearer: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'token', value: auth.token || '' }, + { key: 'prefix', value: auth.prefix || '' }, + ], + }; + case 'basic': + return { + type: 'basic', + basic: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'useISO88591', value: auth.useISO88591 ? 'true' : 'false' }, + { key: 'username', value: auth.username || '' }, + { key: 'password', value: auth.password || '' }, + ], + }; + case 'digest': + return { + type: 'digest', + digest: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'username', value: auth.username || '' }, + { key: 'password', value: auth.password || '' }, + ], + }; + case 'ntlm': + return { + type: 'ntlm', + ntlm: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'username', value: auth.username || '' }, + { key: 'password', value: auth.password || '' }, + ], + }; + case 'oauth1': + return { + type: 'oauth1', + oauth1: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'signatureMethod', value: auth.signatureMethod || '' }, + { key: 'consumerKey', value: auth.consumerKey || '' }, + { key: 'consumerSecret', value: auth.consumerSecret || '' }, + { key: 'token', value: auth.tokenKey || '' }, + + { key: 'tokenSecret', value: auth.tokenSecret || '' }, + { key: 'privateKey', value: auth.privateKey || '' }, + { key: 'version', value: auth.version || '' }, + { key: 'nonce', value: auth.nonce || '' }, + { key: 'timestamp', value: auth.timestamp || '' }, + + { key: 'callback', value: auth.callback || '' }, + { key: 'realm', value: auth.realm || '' }, + { key: 'verifier', value: auth.verifier || '' }, + { key: 'includeBodyHash', value: auth.includeBodyHash ? 'true' : 'false' }, + ], + }; + case 'oauth2': { + const inputGrantType = auth.grantType; + const grantType = (() => { + switch (inputGrantType) { + case 'authorization_code': + return auth.usePkce ? 'authorization_code_with_pkce' : 'authorization_code'; + case 'implicit': + return 'implicit'; + case 'password': + return 'password_credentials'; + case 'client_credentials': + return 'client_credentials'; + case 'refresh_token': + return 'refresh_token'; + default: + throw Error(`auth transforming(toPreRequestAuth): unknown auth grant type for oauth2: ${inputGrantType}`); + } + })(); + + return { + type: 'oauth2', + oauth2: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + + { key: 'grant_type', value: grantType }, + { key: 'authUrl', value: auth.authorizationUrl || '' }, + { key: 'accessTokenUrl', value: auth.accessTokenUrl || '' }, + { key: 'clientId', value: auth.clientId || '' }, + { key: 'clientSecret', value: auth.clientSecret || '' }, + { key: 'scope', value: auth.scope || '' }, + + { key: 'code_verifier', value: auth.code || '' }, + { key: 'accessToken', value: auth.accessToken || '' }, + { key: 'challengeAlgorithm', value: auth.pkceMethod || '' }, + // { key: 'scope', value: auth.usePkce || ''}, + { key: 'username', value: auth.username || '' }, + { key: 'password', value: auth.password || '' }, + + { key: 'callBackUrl', value: auth.redirectUrl || '' }, + { key: 'state', value: auth.state || '' }, + { key: 'refreshTokenUrl', value: auth.refreshToken || '' }, + { key: 'client_authentication', value: auth.credentialsInBody ? 'body' : 'header' }, + + { + key: 'tokenRequestParams', + value: [ + { + key: 'audience', + value: auth.audience || '', + enabled: true, + send_as: 'request_url', // request_body or request_header + }, + { + key: 'resource', + value: auth.resource || '', + enabled: true, + send_as: 'request_url', // request_body or request_header + }, + ], + }, + + // following properties are not supported in script side, still set them + { key: 'tokenPrefix', value: auth.tokenPrefix || '' }, + { key: 'response_type', value: auth.responseType || '' }, + { key: 'origin', value: auth.origin || '' }, + ], + }; } + case 'iam': + return { + type: 'awsv4', + awsv4: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'accessKey', value: auth.accessKeyId || '' }, + { key: 'secretKey', value: auth.secretAccessKey || '' }, + { key: 'sessionToken', value: auth.sessionToken || '' }, + { key: 'region', value: auth.region || '' }, + { key: 'service', value: auth.service || '' }, + ], + }; + case 'hawk': + return { + type: 'hawk', + hawk: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'algorithm', value: auth.algorithm || '' }, + { key: 'authId', value: auth.id || '' }, + { key: 'authKey', value: auth.key || '' }, + { key: 'extraData', value: auth.ext || '' }, + { key: 'validatePayload', value: auth.validatePayload ? 'true' : 'false' }, + // TODO(george): these fields are not supported in Insomnia side + { key: 'timestamp', value: '' }, + { key: 'delegation', value: '' }, + { key: 'app', value: '' }, + { key: 'nonce', value: '' }, + { key: 'user', value: '' }, + { key: 'includePayloadHash', value: 'false' }, + ], + }; + case 'asap': + return { + type: 'asap', + asap: [ + { key: 'disabled', value: auth.disabled ? 'true' : 'false' }, + { key: 'iss', value: auth.issuer || '' }, + { key: 'sub', value: auth.subject || '' }, + { key: 'aud', value: auth.audience || '' }, + { key: 'claims', value: auth.additionalClaims || '' }, + { key: 'kid', value: auth.keyId || '' }, + { key: 'privateKey', value: auth.privateKey || '' }, + ], + }; + case 'netrc': + // TODO: not supported yet + throw Error('netrc auth is not supported in scripting yet'); + default: + // @ts-expect-error - user can input any string + throw Error(`unknown auth type: ${auth.type}`); + } } diff --git a/packages/insomnia-scripting-environment/src/objects/certificates.ts b/packages/insomnia-scripting-environment/src/objects/certificates.ts index b5dd36ef81..025c7fb418 100644 --- a/packages/insomnia-scripting-environment/src/objects/certificates.ts +++ b/packages/insomnia-scripting-environment/src/objects/certificates.ts @@ -2,65 +2,61 @@ import { Property } from './properties'; import { UrlMatchPattern, UrlMatchPatternList } from './urls'; export interface SrcRef { - src: string; // src is the path of the file + src: string; // src is the path of the file } export interface CertificateOptions { - name?: string; - matches?: string[]; - key?: SrcRef; - cert?: SrcRef; - passphrase?: string; - pfx?: SrcRef; // PFX or PKCS12 Certificate - disabled?: boolean; + name?: string; + matches?: string[]; + key?: SrcRef; + cert?: SrcRef; + passphrase?: string; + pfx?: SrcRef; // PFX or PKCS12 Certificate + disabled?: boolean; } export class Certificate extends Property { - override _kind = 'Certificate'; + override _kind = 'Certificate'; - override name?: string; - matches?: UrlMatchPatternList; - key?: SrcRef; - cert?: SrcRef; - passphrase?: string; - pfx?: SrcRef; // PFX or PKCS12 Certificate + override name?: string; + matches?: UrlMatchPatternList; + key?: SrcRef; + cert?: SrcRef; + passphrase?: string; + pfx?: SrcRef; // PFX or PKCS12 Certificate - constructor(options: CertificateOptions) { - super(); + constructor(options: CertificateOptions) { + super(); - this.name = options.name; - this.matches = new UrlMatchPatternList( - undefined, - options.matches ? - options.matches.map(matchStr => new UrlMatchPattern(matchStr)) : - [], - ); - this.key = options.key; - this.cert = options.cert; - this.passphrase = options.passphrase; - this.pfx = options.pfx; - this.disabled = options.disabled; - } + this.name = options.name; + this.matches = new UrlMatchPatternList( + undefined, + options.matches ? options.matches.map(matchStr => new UrlMatchPattern(matchStr)) : [], + ); + this.key = options.key; + this.cert = options.cert; + this.passphrase = options.passphrase; + this.pfx = options.pfx; + this.disabled = options.disabled; + } - static isCertificate(obj: object) { - return '_kind' in obj && obj._kind === 'Certificate'; - } + static isCertificate(obj: object) { + return '_kind' in obj && obj._kind === 'Certificate'; + } - canApplyTo(url: string) { - return this.matches ? this.matches.test(url) : false; - } + canApplyTo(url: string) { + return this.matches ? this.matches.test(url) : false; + } - update(options: CertificateOptions) { - this.name = options.name; - this.matches = new UrlMatchPatternList( - undefined, - options.matches ? - options.matches.map(matchStr => new UrlMatchPattern(matchStr)) : - [], - ); - this.key = options.key; - this.cert = options.cert; - this.passphrase = options.passphrase; - this.pfx = options.pfx; - } + update(options: CertificateOptions) { + this.name = options.name; + this.matches = new UrlMatchPatternList( + undefined, + options.matches ? options.matches.map(matchStr => new UrlMatchPattern(matchStr)) : [], + ); + this.key = options.key; + this.cert = options.cert; + this.passphrase = options.passphrase; + this.pfx = options.pfx; + } } diff --git a/packages/insomnia-scripting-environment/src/objects/console.ts b/packages/insomnia-scripting-environment/src/objects/console.ts index c55b02ca9c..64af491ecc 100644 --- a/packages/insomnia-scripting-environment/src/objects/console.ts +++ b/packages/insomnia-scripting-environment/src/objects/console.ts @@ -1,81 +1,77 @@ type LogLevel = 'debug' | 'info' | 'log' | 'warn' | 'error'; export interface Row { - value: string; - name: string; - timestamp: number; + value: string; + name: string; + timestamp: number; } class Console { - rows: Row[] = []; + rows: Row[] = []; - // TODO: support replacing substitution - printLog = (rows: Row[], level: LogLevel, ...values: any) => { - try { - const content = values.map( - (value: any) => { - return typeof value === 'string' ? value : JSON.stringify(value, null, 2); - } - ).join(' '); + // TODO: support replacing substitution + printLog = (rows: Row[], level: LogLevel, ...values: any) => { + try { + const content = values + .map((value: any) => { + return typeof value === 'string' ? value : JSON.stringify(value, null, 2); + }) + .join(' '); - const row = { - value: `${level}: ${content}`, - name: 'Text', - timestamp: Date.now(), - }; + const row = { + value: `${level}: ${content}`, + name: 'Text', + timestamp: Date.now(), + }; - rows.push(row); - } catch (e) { - rows.push({ - value: 'error: ' + JSON.stringify(e, null, 2), - name: 'Text', - timestamp: Date.now(), - }); - } - }; + rows.push(row); + } catch (e) { + rows.push({ + value: 'error: ' + JSON.stringify(e, null, 2), + name: 'Text', + timestamp: Date.now(), + }); + } + }; - log = (...values: any[]) => { - this.printLog(this.rows, 'log', ...values); - }; + log = (...values: any[]) => { + this.printLog(this.rows, 'log', ...values); + }; - warn = (...values: any[]) => { - this.printLog(this.rows, 'warn', ...values); - }; + warn = (...values: any[]) => { + this.printLog(this.rows, 'warn', ...values); + }; - debug = (...values: any[]) => { - this.printLog(this.rows, 'debug', ...values); - }; + debug = (...values: any[]) => { + this.printLog(this.rows, 'debug', ...values); + }; - info = (...values: any[]) => { - this.printLog(this.rows, 'info', ...values); - }; + info = (...values: any[]) => { + this.printLog(this.rows, 'info', ...values); + }; - error = (...values: any[]) => { - this.printLog(this.rows, 'error', ...values); - }; + error = (...values: any[]) => { + this.printLog(this.rows, 'error', ...values); + }; + clear = (_level: LogLevel, _message?: any, ..._optionalParams: any[]) => { + throw Error('currently "clear" is not supported for the timeline'); + }; - clear = (_level: LogLevel, _message?: any, ..._optionalParams: any[]) => { - throw Error('currently "clear" is not supported for the timeline'); - }; + dumpLogs = () => { + return this.rows.map(row => JSON.stringify(row) + '\n').join('\n'); + }; - dumpLogs = () => { - return this.rows - .map(row => JSON.stringify(row) + '\n') - .join('\n'); - }; - - dumpLogsAsArray = () => { - return this.rows - .map(row => JSON.stringify(row) + '\n'); - }; + dumpLogsAsArray = () => { + return this.rows.map(row => JSON.stringify(row) + '\n'); + }; } let builtInConsole = new Console(); export function getExistingConsole() { - return builtInConsole; + return builtInConsole; } export function getNewConsole() { - builtInConsole = new Console(); - return builtInConsole; + builtInConsole = new Console(); + return builtInConsole; } diff --git a/packages/insomnia-scripting-environment/src/objects/cookies.ts b/packages/insomnia-scripting-environment/src/objects/cookies.ts index fc4bb89781..6cce508d64 100644 --- a/packages/insomnia-scripting-environment/src/objects/cookies.ts +++ b/packages/insomnia-scripting-environment/src/objects/cookies.ts @@ -6,372 +6,363 @@ import { getExistingConsole } from './console'; import { Property, PropertyList } from './properties'; export interface InsomniaCookieExtensions { - creation?: Date; - creationIndex?: number; - lastAccessed?: Date; - pathIsDefault?: boolean; -}; + creation?: Date; + creationIndex?: number; + lastAccessed?: Date; + pathIsDefault?: boolean; +} export interface CookieOptions extends InsomniaCookieExtensions { - id?: string; - key: string; - value: string; - expires?: Date | string | null; - maxAge?: number | 'Infinity' | '-Infinity'; - domain?: string; - path?: string; - secure?: boolean; - httpOnly?: boolean; - hostOnly?: boolean; - session?: boolean; - extensions?: { key: string; value: string }[]; + id?: string; + key: string; + value: string; + expires?: Date | string | null; + maxAge?: number | 'Infinity' | '-Infinity'; + domain?: string; + path?: string; + secure?: boolean; + httpOnly?: boolean; + hostOnly?: boolean; + session?: boolean; + extensions?: { key: string; value: string }[]; } export class Cookie extends Property { - override readonly _kind: string = 'Cookie'; - protected cookie: ToughCookie; - private extensions?: { key: string; value: string }[]; - private insoExtensions: InsomniaCookieExtensions = {}; + override readonly _kind: string = 'Cookie'; + protected cookie: ToughCookie; + private extensions?: { key: string; value: string }[]; + private insoExtensions: InsomniaCookieExtensions = {}; - constructor(cookieDef: CookieOptions | string) { - super(); + constructor(cookieDef: CookieOptions | string) { + super(); - if (typeof cookieDef === 'string') { - const cookieDefParsed = Cookie.parse(cookieDef); - if (!cookieDefParsed) { - throw Error('failed to parse cookie, the cookie string seems invalid'); - } - cookieDef = cookieDefParsed; - } - - const def = { ...cookieDef }; - this.extensions = def.extensions ? [...def.extensions] : []; - def.extensions = []; - - const cookie = ToughCookie.fromJSON(def); - if (!cookie) { - throw Error('failed to parse cookie, the cookie string seems invalid'); - } - - this.id = cookieDef.id || ''; - this.cookie = cookie; - this.insoExtensions = { - creation: cookieDef.creation, - creationIndex: cookieDef.creationIndex, - lastAccessed: cookieDef.lastAccessed, - pathIsDefault: cookieDef.pathIsDefault, - }; + if (typeof cookieDef === 'string') { + const cookieDefParsed = Cookie.parse(cookieDef); + if (!cookieDefParsed) { + throw Error('failed to parse cookie, the cookie string seems invalid'); + } + cookieDef = cookieDefParsed; } - static override _index = 'key'; + const def = { ...cookieDef }; + this.extensions = def.extensions ? [...def.extensions] : []; + def.extensions = []; - static isCookie(obj: Property) { - return '_kind' in obj && obj._kind === 'Cookie'; + const cookie = ToughCookie.fromJSON(def); + if (!cookie) { + throw Error('failed to parse cookie, the cookie string seems invalid'); } - static parse(cookieStr: string) { - const cookieObj = ToughCookie.parse(cookieStr, { loose: true }); - if (!cookieObj) { - throw Error('failed to parse cookie, the cookie string seems invalid'); - } - - const hostOnly = cookieObj.extensions?.includes('HostOnly') || false; - const session = cookieObj.extensions?.includes('Session') || false; - if (hostOnly) { - cookieObj.extensions = cookieObj.extensions?.filter(item => item !== 'HostOnly') || []; - } - if (session) { - cookieObj.extensions = cookieObj.extensions?.filter(item => item !== 'Session') || []; - } - - // Tough Cookies extensions works well with string[], but not {key: string; value: string}[] - const extensions = cookieObj.extensions?.map((entry: string | { key: string; value: string }) => { - if (typeof entry === 'string') { - const equalPos = entry.indexOf('='); - if (equalPos > 0) { - return { key: entry.slice(0, equalPos), value: entry.slice(equalPos + 1) }; - } - return { key: entry, value: 'true' }; - } else if ( - 'key' in entry && - 'value' in entry && - typeof entry.key === 'string' && - typeof entry.value === 'string' - ) { - return { key: entry.key, value: entry.value }; - } - throw Error('failed to create cookie, extension must be: { key: string; value: string }[]'); - - - }); - - return { - key: cookieObj.key, - value: cookieObj.value, - expires: cookieObj.expires || undefined, - maxAge: cookieObj.maxAge, - domain: cookieObj.domain || undefined, - path: cookieObj.path || undefined, - secure: cookieObj.secure || false, - httpOnly: cookieObj.httpOnly || false, - hostOnly, - session, - extensions: extensions, - }; - } - - static stringify(cookie: Cookie) { - return cookie.toString(); - } - - static unparseSingle(cookieOpt: CookieOptions) { - const cookie = new Cookie(cookieOpt); - if (!cookie) { - throw Error('failed to unparse cookie, the cookie options seems invalid'); - } - return cookie.toString(); - } - - static unparse(cookies: Cookie[]) { - const cookieStrs = cookies.map(cookie => cookie.toString()); - return cookieStrs.join('; '); - } - - override toString = () => { - const hostOnlyPart = this.cookie.hostOnly ? '; HostOnly' : ''; - const sessionPart = this.cookie.extensions?.includes('session') ? '; Session' : ''; - const extensionPart = this.extensions && this.extensions.length > 0 ? - '; ' + this.extensions.map(ext => `${ext.key}=${ext.value}`).join(';') : - ''; - - return this.cookie.toString() + hostOnlyPart + sessionPart + extensionPart; + this.id = cookieDef.id || ''; + this.cookie = cookie; + this.insoExtensions = { + creation: cookieDef.creation, + creationIndex: cookieDef.creationIndex, + lastAccessed: cookieDef.lastAccessed, + pathIsDefault: cookieDef.pathIsDefault, }; + } - override valueOf = () => { - return this.cookie.toJSON().value; - }; + static override _index = 'key'; - get key() { - return this.cookie.toJSON().key; - }; + static isCookie(obj: Property) { + return '_kind' in obj && obj._kind === 'Cookie'; + } - override toJSON = () => { - return { - id: this.id, - key: this.cookie.key, - value: this.cookie.value, - expires: this.cookie.expires === 'Infinity' ? undefined : this.cookie.expires, - maxAge: this.cookie.maxAge, - domain: this.cookie.domain, - path: this.cookie.path, - secure: this.cookie.secure, - httpOnly: this.cookie.httpOnly, - hostOnly: this.cookie.hostOnly, - session: this.cookie.extensions?.includes('session'), - extensions: this.extensions, - // extra fields from Insomnia - creation: this.insoExtensions.creation, - creationIndex: this.insoExtensions.creationIndex, - lastAccessed: this.insoExtensions.lastAccessed, - pathIsDefault: this.insoExtensions.pathIsDefault, - }; - }; -} - -export class CookieList extends PropertyList { - override _kind = 'CookieList'; - - constructor(cookies: Cookie[]) { - super( - Cookie, - undefined, - cookies, - ); + static parse(cookieStr: string) { + const cookieObj = ToughCookie.parse(cookieStr, { loose: true }); + if (!cookieObj) { + throw Error('failed to parse cookie, the cookie string seems invalid'); } - static isCookieList(obj: object) { - return '_kind' in obj && obj._kind === 'CookieList'; + const hostOnly = cookieObj.extensions?.includes('HostOnly') || false; + const session = cookieObj.extensions?.includes('Session') || false; + if (hostOnly) { + cookieObj.extensions = cookieObj.extensions?.filter(item => item !== 'HostOnly') || []; } -} - -export class CookieObject extends CookieList { - private cookieJar: CookieJar; - - constructor(cookieJar: InsomniaCookieJar | null) { - const cookies = cookieJar - ? cookieJar.cookies.map((cookie: InsomniaCookie): Cookie => { - let expires: string | Date | null = null; - if (cookie.expires || cookie.expires === 0) { - if (typeof cookie.expires === 'number') { - expires = new Date(cookie.expires); - } else { - expires = cookie.expires; - } - } - - return new Cookie({ - id: cookie.id, - key: cookie.key, - value: cookie.value, - expires: expires, - maxAge: undefined, // not supported in Insomnia - domain: cookie.domain, - path: cookie.path, - secure: cookie.secure, - httpOnly: cookie.httpOnly, - hostOnly: cookie.hostOnly, - session: undefined, // not supported in Insomnia - extensions: undefined, // TODO: its format from Insomnia is unknown - // follows are properties from Insomnia - creation: cookie.creation, - creationIndex: cookie.creationIndex, - lastAccessed: cookie.lastAccessed, - pathIsDefault: cookie.pathIsDefault, - }); - }) - : []; - - super(cookies); - const scriptCookieJar = cookieJar ? new CookieJar(cookieJar.name, cookies) : new CookieJar('', []); - this.cookieJar = scriptCookieJar; - this.typeClass = Cookie; + if (session) { + cookieObj.extensions = cookieObj.extensions?.filter(item => item !== 'Session') || []; } - jar() { - return this.cookieJar; - } -} - -export class CookieJar { - // CookieJar from tough-cookie can not be used, as it will fail in comparing context location and cookies' domain - // as it reads location from the browser window, it is "localhost" - private jar: Map>; // Map> - private jarName: string; - - constructor(jarName: string, cookies?: Cookie[]) { - this.jarName = jarName; - this.jar = new Map(); - - if (cookies) { - cookies.forEach(cookie => { - const properties = cookie.toJSON(); - if (!properties.domain) { - getExistingConsole().warn(`domain is not specified for the cookie "${cookie.key}" so it is omitted`); - return; - } - - const domainCookies = this.jar.get(properties.domain) || new Map(); - this.jar.set(properties.domain, domainCookies.set(properties.key, cookie)); - }); + // Tough Cookies extensions works well with string[], but not {key: string; value: string}[] + const extensions = cookieObj.extensions?.map((entry: string | { key: string; value: string }) => { + if (typeof entry === 'string') { + const equalPos = entry.indexOf('='); + if (equalPos > 0) { + return { key: entry.slice(0, equalPos), value: entry.slice(equalPos + 1) }; } - } - - set(url: string, key: string, value: string | CookieOptions, cb: (error?: Error, cookie?: Cookie) => void) { - const domainCookies = this.jar.get(url) || new Map(); - if (typeof value === 'string') { - const domainCookie = new Cookie({ - key: key, - value: value, - domain: url, - }); - this.jar.set(url, domainCookies.set(key, domainCookie)); - cb(undefined, domainCookie); - } else { - const domainCookie = new Cookie(value); - this.jar.set(url, domainCookies.set(key, domainCookie)); - cb(undefined, domainCookie); - } - } - - // TODO: create a better method for setting cookie, or overload the above method - // set( - // url: string, - // info: { name: string; value: string; httpOnly: boolean }, - // cb: (error?: Error, cookie?: Cookie) => void, - // ) { - // try { - // const cookie = new ToughCookie({ key: info.name, value: info.value, httpOnly: info.httpOnly }); - // this.jar.setCookieSync(cookie, url, { http: info.httpOnly }); - // cb(undefined, new Cookie({ key: info.name, value: info.value, httpOnly: info.httpOnly })); - // } catch (e) { - // cb(e, undefined); - // } - // } - - get(url: string, name: string, cb: (error?: Error, cookie?: Cookie) => void) { - const domainCookies = this.jar.get(url) || new Map(); - cb(undefined, domainCookies.get(name)); - } - - getAll(url: string, cb: (error?: Error, cookies?: Cookie[]) => void) { - const domainCookies = this.jar.get(url) || new Map(); - cb( - undefined, - Array.from(domainCookies.values()), - ); - } - - unset(url: string, name: string, cb: (error?: Error | null) => void) { - const domainCookies = this.jar.get(url); - if (!domainCookies) { - cb(undefined); - } else { - domainCookies.delete(name); - cb(undefined); - } - } - - clear(url: string, cb: (error?: Error | null) => void) { - this.jar.delete(url); - cb(undefined); - } - - toInsomniaCookieJar() { - const cookies = new Array>(); - Array.from(this.jar.values()) - .forEach((domainCookies: Map) => { - Array.from(domainCookies.values()).forEach(cookie => { - const cookieObj = cookie.toJSON(); - cookies.push({ - id: cookieObj.id, - key: cookieObj.key, - value: cookieObj.value, - expires: cookieObj.expires || 'Infinity', // transform it back to 'Infinity', avoid edge cases - domain: cookieObj.domain || undefined, - path: cookieObj.path || undefined, - secure: cookieObj.secure, - httpOnly: cookieObj.httpOnly, - extensions: cookieObj.extensions || undefined, - creation: cookieObj.creation, - creationIndex: cookieObj.creationIndex, - hostOnly: cookieObj.hostOnly || undefined, - pathIsDefault: cookieObj.pathIsDefault, - lastAccessed: cookieObj.lastAccessed, - }); - }); - }); - - return { - name: this.jarName, - cookies, - }; - } -} - -export function mergeCookieJar( - originalCookieJar: InsomniaCookieJar, - updatedCookieJar: { name: string; cookies: Partial[] }, -): InsomniaCookieJar { - const cookiesWithId = updatedCookieJar.cookies.map((cookie): InsomniaCookie => { - if (!cookie.id) { - // this follows the generation approach in the `cookie-list.tsx` - cookie.id = uuidv4(); - } - return cookie as InsomniaCookie; + return { key: entry, value: 'true' }; + } else if ( + 'key' in entry && + 'value' in entry && + typeof entry.key === 'string' && + typeof entry.value === 'string' + ) { + return { key: entry.key, value: entry.value }; + } + throw Error('failed to create cookie, extension must be: { key: string; value: string }[]'); }); return { - ...originalCookieJar, - cookies: cookiesWithId, + key: cookieObj.key, + value: cookieObj.value, + expires: cookieObj.expires || undefined, + maxAge: cookieObj.maxAge, + domain: cookieObj.domain || undefined, + path: cookieObj.path || undefined, + secure: cookieObj.secure || false, + httpOnly: cookieObj.httpOnly || false, + hostOnly, + session, + extensions: extensions, }; + } + + static stringify(cookie: Cookie) { + return cookie.toString(); + } + + static unparseSingle(cookieOpt: CookieOptions) { + const cookie = new Cookie(cookieOpt); + if (!cookie) { + throw Error('failed to unparse cookie, the cookie options seems invalid'); + } + return cookie.toString(); + } + + static unparse(cookies: Cookie[]) { + const cookieStrs = cookies.map(cookie => cookie.toString()); + return cookieStrs.join('; '); + } + + override toString = () => { + const hostOnlyPart = this.cookie.hostOnly ? '; HostOnly' : ''; + const sessionPart = this.cookie.extensions?.includes('session') ? '; Session' : ''; + const extensionPart = + this.extensions && this.extensions.length > 0 + ? '; ' + this.extensions.map(ext => `${ext.key}=${ext.value}`).join(';') + : ''; + + return this.cookie.toString() + hostOnlyPart + sessionPart + extensionPart; + }; + + override valueOf = () => { + return this.cookie.toJSON().value; + }; + + get key() { + return this.cookie.toJSON().key; + } + + override toJSON = () => { + return { + id: this.id, + key: this.cookie.key, + value: this.cookie.value, + expires: this.cookie.expires === 'Infinity' ? undefined : this.cookie.expires, + maxAge: this.cookie.maxAge, + domain: this.cookie.domain, + path: this.cookie.path, + secure: this.cookie.secure, + httpOnly: this.cookie.httpOnly, + hostOnly: this.cookie.hostOnly, + session: this.cookie.extensions?.includes('session'), + extensions: this.extensions, + // extra fields from Insomnia + creation: this.insoExtensions.creation, + creationIndex: this.insoExtensions.creationIndex, + lastAccessed: this.insoExtensions.lastAccessed, + pathIsDefault: this.insoExtensions.pathIsDefault, + }; + }; +} + +export class CookieList extends PropertyList { + override _kind = 'CookieList'; + + constructor(cookies: Cookie[]) { + super(Cookie, undefined, cookies); + } + + static isCookieList(obj: object) { + return '_kind' in obj && obj._kind === 'CookieList'; + } +} + +export class CookieObject extends CookieList { + private cookieJar: CookieJar; + + constructor(cookieJar: InsomniaCookieJar | null) { + const cookies = cookieJar + ? cookieJar.cookies.map((cookie: InsomniaCookie): Cookie => { + let expires: string | Date | null = null; + if (cookie.expires || cookie.expires === 0) { + if (typeof cookie.expires === 'number') { + expires = new Date(cookie.expires); + } else { + expires = cookie.expires; + } + } + + return new Cookie({ + id: cookie.id, + key: cookie.key, + value: cookie.value, + expires: expires, + maxAge: undefined, // not supported in Insomnia + domain: cookie.domain, + path: cookie.path, + secure: cookie.secure, + httpOnly: cookie.httpOnly, + hostOnly: cookie.hostOnly, + session: undefined, // not supported in Insomnia + extensions: undefined, // TODO: its format from Insomnia is unknown + // follows are properties from Insomnia + creation: cookie.creation, + creationIndex: cookie.creationIndex, + lastAccessed: cookie.lastAccessed, + pathIsDefault: cookie.pathIsDefault, + }); + }) + : []; + + super(cookies); + const scriptCookieJar = cookieJar ? new CookieJar(cookieJar.name, cookies) : new CookieJar('', []); + this.cookieJar = scriptCookieJar; + this.typeClass = Cookie; + } + + jar() { + return this.cookieJar; + } +} + +export class CookieJar { + // CookieJar from tough-cookie can not be used, as it will fail in comparing context location and cookies' domain + // as it reads location from the browser window, it is "localhost" + private jar: Map>; // Map> + private jarName: string; + + constructor(jarName: string, cookies?: Cookie[]) { + this.jarName = jarName; + this.jar = new Map(); + + if (cookies) { + cookies.forEach(cookie => { + const properties = cookie.toJSON(); + if (!properties.domain) { + getExistingConsole().warn(`domain is not specified for the cookie "${cookie.key}" so it is omitted`); + return; + } + + const domainCookies = this.jar.get(properties.domain) || new Map(); + this.jar.set(properties.domain, domainCookies.set(properties.key, cookie)); + }); + } + } + + set(url: string, key: string, value: string | CookieOptions, cb: (error?: Error, cookie?: Cookie) => void) { + const domainCookies = this.jar.get(url) || new Map(); + if (typeof value === 'string') { + const domainCookie = new Cookie({ + key: key, + value: value, + domain: url, + }); + this.jar.set(url, domainCookies.set(key, domainCookie)); + cb(undefined, domainCookie); + } else { + const domainCookie = new Cookie(value); + this.jar.set(url, domainCookies.set(key, domainCookie)); + cb(undefined, domainCookie); + } + } + + // TODO: create a better method for setting cookie, or overload the above method + // set( + // url: string, + // info: { name: string; value: string; httpOnly: boolean }, + // cb: (error?: Error, cookie?: Cookie) => void, + // ) { + // try { + // const cookie = new ToughCookie({ key: info.name, value: info.value, httpOnly: info.httpOnly }); + // this.jar.setCookieSync(cookie, url, { http: info.httpOnly }); + // cb(undefined, new Cookie({ key: info.name, value: info.value, httpOnly: info.httpOnly })); + // } catch (e) { + // cb(e, undefined); + // } + // } + + get(url: string, name: string, cb: (error?: Error, cookie?: Cookie) => void) { + const domainCookies = this.jar.get(url) || new Map(); + cb(undefined, domainCookies.get(name)); + } + + getAll(url: string, cb: (error?: Error, cookies?: Cookie[]) => void) { + const domainCookies = this.jar.get(url) || new Map(); + cb(undefined, Array.from(domainCookies.values())); + } + + unset(url: string, name: string, cb: (error?: Error | null) => void) { + const domainCookies = this.jar.get(url); + if (!domainCookies) { + cb(undefined); + } else { + domainCookies.delete(name); + cb(undefined); + } + } + + clear(url: string, cb: (error?: Error | null) => void) { + this.jar.delete(url); + cb(undefined); + } + + toInsomniaCookieJar() { + const cookies = new Array>(); + Array.from(this.jar.values()).forEach((domainCookies: Map) => { + Array.from(domainCookies.values()).forEach(cookie => { + const cookieObj = cookie.toJSON(); + cookies.push({ + id: cookieObj.id, + key: cookieObj.key, + value: cookieObj.value, + expires: cookieObj.expires || 'Infinity', // transform it back to 'Infinity', avoid edge cases + domain: cookieObj.domain || undefined, + path: cookieObj.path || undefined, + secure: cookieObj.secure, + httpOnly: cookieObj.httpOnly, + extensions: cookieObj.extensions || undefined, + creation: cookieObj.creation, + creationIndex: cookieObj.creationIndex, + hostOnly: cookieObj.hostOnly || undefined, + pathIsDefault: cookieObj.pathIsDefault, + lastAccessed: cookieObj.lastAccessed, + }); + }); + }); + + return { + name: this.jarName, + cookies, + }; + } +} + +export function mergeCookieJar( + originalCookieJar: InsomniaCookieJar, + updatedCookieJar: { name: string; cookies: Partial[] }, +): InsomniaCookieJar { + const cookiesWithId = updatedCookieJar.cookies.map((cookie): InsomniaCookie => { + if (!cookie.id) { + // this follows the generation approach in the `cookie-list.tsx` + cookie.id = uuidv4(); + } + return cookie as InsomniaCookie; + }); + + return { + ...originalCookieJar, + cookies: cookiesWithId, + }; } diff --git a/packages/insomnia-scripting-environment/src/objects/environments.ts b/packages/insomnia-scripting-environment/src/objects/environments.ts index 885fcdf0cb..0ae2f27762 100644 --- a/packages/insomnia-scripting-environment/src/objects/environments.ts +++ b/packages/insomnia-scripting-environment/src/objects/environments.ts @@ -2,181 +2,181 @@ import { getExistingConsole } from './console'; import { getInterpolator } from './interpolator'; export class Environment { - private _name: string; - private kvs = new Map(); + private _name: string; + private kvs = new Map(); - constructor(name: string, jsonObject: object | undefined) { - this._name = name; - this.kvs = new Map(Object.entries(jsonObject || {})); + constructor(name: string, jsonObject: object | undefined) { + this._name = name; + this.kvs = new Map(Object.entries(jsonObject || {})); + } + + get name() { + return this._name; + } + + has = (variableName: string) => { + return this.kvs.has(variableName); + }; + + get = (variableName: string) => { + return this.kvs.get(variableName); + }; + + set = (variableName: string, variableValue: boolean | number | string | undefined | null) => { + if (variableValue === null) { + getExistingConsole().warn(`Variable "${variableName}" has a null value`); + return; } + this.kvs.set(variableName, variableValue); + }; - get name() { - return this._name; - } + unset = (variableName: string) => { + this.kvs.delete(variableName); + }; - has = (variableName: string) => { - return this.kvs.has(variableName); - }; + clear = () => { + this.kvs.clear(); + }; - get = (variableName: string) => { - return this.kvs.get(variableName); - }; + replaceIn = (template: string) => { + return getInterpolator().render(template, this.toObject()); + }; - set = (variableName: string, variableValue: boolean | number | string | undefined | null) => { - if (variableValue === null) { - getExistingConsole().warn(`Variable "${variableName}" has a null value`); - return; - } - this.kvs.set(variableName, variableValue); - }; - - unset = (variableName: string) => { - this.kvs.delete(variableName); - }; - - clear = () => { - this.kvs.clear(); - }; - - replaceIn = (template: string) => { - return getInterpolator().render(template, this.toObject()); - }; - - toObject = () => { - return Object.fromEntries(this.kvs.entries()); - }; + toObject = () => { + return Object.fromEntries(this.kvs.entries()); + }; } function mergeFolderLevelVars(folderLevelVars: Environment[]) { - const mergedFolderLevelObject = folderLevelVars.reduce((merged: object, folderLevelEnv: Environment) => { - return { ...merged, ...folderLevelEnv.toObject() }; - }, {}); - return new Environment('mergedFolderLevelVars', mergedFolderLevelObject); + const mergedFolderLevelObject = folderLevelVars.reduce((merged: object, folderLevelEnv: Environment) => { + return { ...merged, ...folderLevelEnv.toObject() }; + }, {}); + return new Environment('mergedFolderLevelVars', mergedFolderLevelObject); } export class Variables { - // TODO: support vars for all levels - private globalVars: Environment; - private collectionVars: Environment; - private environmentVars: Environment; - private iterationDataVars: Environment; - private folderLevelVars: Environment[]; - private localVars: Environment; + // TODO: support vars for all levels + private globalVars: Environment; + private collectionVars: Environment; + private environmentVars: Environment; + private iterationDataVars: Environment; + private folderLevelVars: Environment[]; + private localVars: Environment; - constructor( - args: { - globalVars: Environment; - collectionVars: Environment; - environmentVars: Environment; - iterationDataVars: Environment; - folderLevelVars: Environment[]; - localVars: Environment; - }, - ) { - this.globalVars = args.globalVars; - this.collectionVars = args.collectionVars; - this.environmentVars = args.environmentVars; - this.iterationDataVars = args.iterationDataVars; - this.folderLevelVars = args.folderLevelVars; - this.localVars = args.localVars; + constructor(args: { + globalVars: Environment; + collectionVars: Environment; + environmentVars: Environment; + iterationDataVars: Environment; + folderLevelVars: Environment[]; + localVars: Environment; + }) { + this.globalVars = args.globalVars; + this.collectionVars = args.collectionVars; + this.environmentVars = args.environmentVars; + this.iterationDataVars = args.iterationDataVars; + this.folderLevelVars = args.folderLevelVars; + this.localVars = args.localVars; + } + + has = (variableName: string) => { + const globalVarsHas = this.globalVars.has(variableName); + const collectionVarsHas = this.collectionVars.has(variableName); + const environmentVarsHas = this.environmentVars.has(variableName); + const iterationDataVarsHas = this.iterationDataVars.has(variableName); + const folderLevelVarsHas = this.folderLevelVars.some(vars => vars.has(variableName)); + const localVarsHas = this.localVars.has(variableName); + + return ( + globalVarsHas || + collectionVarsHas || + environmentVarsHas || + iterationDataVarsHas || + folderLevelVarsHas || + localVarsHas + ); + }; + + get = (variableName: string) => { + let finalVal: boolean | number | string | object | undefined = undefined; + [ + this.localVars, + mergeFolderLevelVars(this.folderLevelVars), + this.iterationDataVars, + this.environmentVars, + this.collectionVars, + this.globalVars, + ].forEach(vars => { + const value = vars.get(variableName); + if (!finalVal && value) { + finalVal = value; + } + }); + + return finalVal; + }; + + set = (variableName: string, variableValue: boolean | number | string | undefined | null) => { + if (variableValue === null) { + getExistingConsole().warn(`Variable "${variableName}" has a null value`); + return; } - has = (variableName: string) => { - const globalVarsHas = this.globalVars.has(variableName); - const collectionVarsHas = this.collectionVars.has(variableName); - const environmentVarsHas = this.environmentVars.has(variableName); - const iterationDataVarsHas = this.iterationDataVars.has(variableName); - const folderLevelVarsHas = this.folderLevelVars.some(vars => vars.has(variableName)); - const localVarsHas = this.localVars.has(variableName); + this.localVars.set(variableName, variableValue); + }; - return globalVarsHas || collectionVarsHas || environmentVarsHas || iterationDataVarsHas || folderLevelVarsHas || localVarsHas; - }; + replaceIn = (template: string) => { + const context = this.toObject(); + return getInterpolator().render(template, context); + }; - get = (variableName: string) => { - let finalVal: boolean | number | string | object | undefined = undefined; - [ - this.localVars, - mergeFolderLevelVars(this.folderLevelVars), - this.iterationDataVars, - this.environmentVars, - this.collectionVars, - this.globalVars, - ].forEach(vars => { - const value = vars.get(variableName); - if (!finalVal && value) { - finalVal = value; - } - }); + toObject = () => { + return [ + this.globalVars, + this.collectionVars, + this.environmentVars, + this.iterationDataVars, + mergeFolderLevelVars(this.folderLevelVars), + this.localVars, + ] + .map(vars => vars.toObject()) + .reduce((ctx, obj) => ({ ...ctx, ...obj }), {}); + }; - return finalVal; - }; - - set = (variableName: string, variableValue: boolean | number | string | undefined | null) => { - if (variableValue === null) { - getExistingConsole().warn(`Variable "${variableName}" has a null value`); - return; - } - - this.localVars.set(variableName, variableValue); - }; - - replaceIn = (template: string) => { - const context = this.toObject(); - return getInterpolator().render(template, context); - }; - - toObject = () => { - return [ - this.globalVars, - this.collectionVars, - this.environmentVars, - this.iterationDataVars, - mergeFolderLevelVars(this.folderLevelVars), - this.localVars, - ].map( - vars => vars.toObject() - ).reduce( - (ctx, obj) => ({ ...ctx, ...obj }), - {}, - ); - }; - - localVarsToObject = () => { - return this.localVars.toObject(); - }; + localVarsToObject = () => { + return this.localVars.toObject(); + }; } export class Vault extends Environment { + constructor(name: string, jsonObject: object | undefined, enableVaultInScripts: boolean) { + super(name, jsonObject); + return new Proxy(this, { + // throw error on get or set method call if enableVaultInScripts is false + get: (target, prop, receiver) => { + if (!enableVaultInScripts) { + throw new Error('Vault is disabled in script'); + } + return Reflect.get(target, prop, receiver); + }, + set: (target, prop, value, receiver) => { + if (!enableVaultInScripts) { + throw new Error('Vault is disabled in script'); + } + return Reflect.set(target, prop, value, receiver); + }, + }); + } - constructor(name: string, jsonObject: object | undefined, enableVaultInScripts: boolean) { - super(name, jsonObject); - return new Proxy(this, { - // throw error on get or set method call if enableVaultInScripts is false - get: (target, prop, receiver) => { - if (!enableVaultInScripts) { - throw new Error('Vault is disabled in script'); - } - return Reflect.get(target, prop, receiver); - }, - set: (target, prop, value, receiver) => { - if (!enableVaultInScripts) { - throw new Error('Vault is disabled in script'); - } - return Reflect.set(target, prop, value, receiver); - }, - }); - } + unset = () => { + throw new Error('Vault can not be unset in script'); + }; - unset = () => { - throw new Error('Vault can not be unset in script'); - }; - - clear = () => { - throw new Error('Vault can not be cleared in script'); - }; - - set = () => { - throw new Error('Vault can not be set in script'); - }; + clear = () => { + throw new Error('Vault can not be cleared in script'); + }; + set = () => { + throw new Error('Vault can not be set in script'); + }; } diff --git a/packages/insomnia-scripting-environment/src/objects/execution.ts b/packages/insomnia-scripting-environment/src/objects/execution.ts index 1167669083..395a933da1 100644 --- a/packages/insomnia-scripting-environment/src/objects/execution.ts +++ b/packages/insomnia-scripting-environment/src/objects/execution.ts @@ -17,7 +17,7 @@ export class Execution { get: (target, prop, receiver) => { if (prop === 'current') { return target.length > 0 ? target[target.length - 1] : ''; - }; + } return Reflect.get(target, prop, receiver); }, }); @@ -26,7 +26,7 @@ export class Execution { } else { throw new Error('Location input must be array of string'); } - }; + } skipRequest = () => { this._skipRequest = true; @@ -43,4 +43,4 @@ export class Execution { nextRequestIdOrName: this._nextRequestIdOrName, }; }; -}; +} diff --git a/packages/insomnia-scripting-environment/src/objects/folders.ts b/packages/insomnia-scripting-environment/src/objects/folders.ts index e12bedd8da..24d6715a6f 100644 --- a/packages/insomnia-scripting-environment/src/objects/folders.ts +++ b/packages/insomnia-scripting-environment/src/objects/folders.ts @@ -2,63 +2,63 @@ import { Environment } from './environments'; // Folder reprensents a request folder in Insomnia. export class Folder { - id: string; - name: string; - environment: Environment; + id: string; + name: string; + environment: Environment; - constructor(id: string, name: string, environmentObject: object | undefined) { - this.id = id; - this.name = name; - this.environment = new Environment(`${id}.environment`, environmentObject); - } + constructor(id: string, name: string, environmentObject: object | undefined) { + this.id = id; + this.name = name; + this.environment = new Environment(`${id}.environment`, environmentObject); + } - toObject = () => { - return { - id: this.id, - name: this.name, - environment: this.environment.toObject(), - }; + toObject = () => { + return { + id: this.id, + name: this.name, + environment: this.environment.toObject(), }; + }; } // ParentFolders reprensents ancestor folders of the active request export class ParentFolders { - constructor(private folders: Folder[]) { } + constructor(private folders: Folder[]) {} - get = (idOrName: string) => { - const folder = this.folders.find(folder => folder.name === idOrName || folder.id === idOrName); - if (!folder) { - throw Error(`Folder "${idOrName}" not found`); - } - return folder; - }; + get = (idOrName: string) => { + const folder = this.folders.find(folder => folder.name === idOrName || folder.id === idOrName); + if (!folder) { + throw Error(`Folder "${idOrName}" not found`); + } + return folder; + }; - getById = (id: string) => { - const folder = this.folders.find(folder => folder.id === id); - if (!folder) { - throw Error(`Folder "${id}" not found`); - } - return folder; - }; + getById = (id: string) => { + const folder = this.folders.find(folder => folder.id === id); + if (!folder) { + throw Error(`Folder "${id}" not found`); + } + return folder; + }; - getByName = (folderName: string) => { - const folder = this.folders.find(folder => folder.name === folderName); - if (!folder) { - throw Error(`Folder "${folderName}" not found`); - } - return folder; - }; + getByName = (folderName: string) => { + const folder = this.folders.find(folder => folder.name === folderName); + if (!folder) { + throw Error(`Folder "${folderName}" not found`); + } + return folder; + }; - findValue = (valueKey: string) => { - const targetEnv = [...this.folders].reverse().find(folder => folder.environment.has(valueKey)); - return targetEnv !== undefined ? targetEnv.environment.get(valueKey) : undefined; - }; + findValue = (valueKey: string) => { + const targetEnv = [...this.folders].reverse().find(folder => folder.environment.has(valueKey)); + return targetEnv !== undefined ? targetEnv.environment.get(valueKey) : undefined; + }; - toObject = () => { - return this.folders.map(folder => folder.toObject()); - }; + toObject = () => { + return this.folders.map(folder => folder.toObject()); + }; - getEnvironments = () => { - return this.folders.map(folder => folder.environment); - }; + getEnvironments = () => { + return this.folders.map(folder => folder.environment); + }; } diff --git a/packages/insomnia-scripting-environment/src/objects/headers.ts b/packages/insomnia-scripting-environment/src/objects/headers.ts index 931fb67e95..9275df047a 100644 --- a/packages/insomnia-scripting-environment/src/objects/headers.ts +++ b/packages/insomnia-scripting-environment/src/objects/headers.ts @@ -1,119 +1,108 @@ import { Property, PropertyList } from './properties'; export interface HeaderDefinition { - key: string; - value: string; - id?: string; - name?: string; - type?: string; - disabled?: boolean; + key: string; + value: string; + id?: string; + name?: string; + type?: string; + disabled?: boolean; } export class Header extends Property { - override _kind = 'Header'; - type = ''; - key: string; - value: string; + override _kind = 'Header'; + type = ''; + key: string; + value: string; - constructor( - opts: HeaderDefinition | string, - name?: string, // if it is defined, it overrides 'key' (not 'name') - ) { - super(); + constructor( + opts: HeaderDefinition | string, + name?: string, // if it is defined, it overrides 'key' (not 'name') + ) { + super(); - if (typeof opts === 'string') { - const obj = Header.parseSingle(opts); - this.key = obj.key; - this.value = obj.value; - } else { - this.id = opts.id ? opts.id : ''; - this.key = opts.key ? opts.key : ''; - this.name = name ? name : (opts.name ? opts.name : ''); - this.value = opts.value ? opts.value : ''; - this.type = opts.type ? opts.type : ''; - this.disabled = opts ? opts.disabled : false; - } + if (typeof opts === 'string') { + const obj = Header.parseSingle(opts); + this.key = obj.key; + this.value = obj.value; + } else { + this.id = opts.id ? opts.id : ''; + this.key = opts.key ? opts.key : ''; + this.name = name ? name : opts.name ? opts.name : ''; + this.value = opts.value ? opts.value : ''; + this.type = opts.type ? opts.type : ''; + this.disabled = opts ? opts.disabled : false; + } + } + + static override _index = 'key'; + + static create(input?: { key: string; value: string } | string, name?: string): Header { + return new Header(input || { key: '', value: '' }, name); + } + + static isHeader(obj: object) { + return '_kind' in obj && obj._kind === 'Header'; + } + + // example: 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n' + static parse(headerString: string): { key: string; value: string }[] { + return headerString + .split('\n') + .filter(kvPart => kvPart.trim() !== '') + .map(kvPart => Header.parseSingle(kvPart)); + } + + static parseSingle(headerStr: string): { key: string; value: string } { + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers + // the first colon is the separator + const separatorPos = headerStr.indexOf(':'); + + if (separatorPos <= 0) { + throw Error('Header.parseSingle: the header string seems invalid'); } - static override _index = 'key'; + const key = headerStr.slice(0, separatorPos); + const value = headerStr.slice(separatorPos + 1); - static create(input?: { key: string; value: string } | string, name?: string): Header { - return new Header(input || { key: '', value: '' }, name); - } + return { key: key.trim(), value: value.trim() }; + } - static isHeader(obj: object) { - return '_kind' in obj && obj._kind === 'Header'; - } + static unparse(headers: { key: string; value: string }[] | PropertyList
, separator?: string): string { + const headerArray: { key: string; value: string }[] = [...headers.map(header => this.unparseSingle(header), {})]; - // example: 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n' - static parse(headerString: string): { key: string; value: string }[] { - return headerString - .split('\n') - .filter(kvPart => kvPart.trim() !== '') - .map(kvPart => Header.parseSingle(kvPart)); - } + return headerArray.join(separator || '\n'); + } - static parseSingle(headerStr: string): { key: string; value: string } { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers - // the first colon is the separator - const separatorPos = headerStr.indexOf(':'); + static unparseSingle(header: { key: string; value: string } | Header): string { + // both PropertyList and object contains 'key' and 'value' + return `${header.key}: ${header.value}`; + } - if (separatorPos <= 0) { - throw Error('Header.parseSingle: the header string seems invalid'); - } + update(newHeader: { key: string; value: string }) { + this.key = newHeader.key; + this.value = newHeader.value; + } - const key = headerStr.slice(0, separatorPos); - const value = headerStr.slice(separatorPos + 1); - - return { key: key.trim(), value: value.trim() }; - } - - static unparse(headers: { key: string; value: string }[] | PropertyList
, separator?: string): string { - const headerArray: { key: string; value: string }[] = [ - ...headers.map( - header => this.unparseSingle(header), {} - ), - ]; - - return headerArray.join(separator || '\n'); - } - - static unparseSingle(header: { key: string; value: string } | Header): string { - // both PropertyList and object contains 'key' and 'value' - return `${header.key}: ${header.value}`; - } - - update(newHeader: { key: string; value: string }) { - this.key = newHeader.key; - this.value = newHeader.value; - } - - override valueOf() { - return this.value; - } + override valueOf() { + return this.value; + } } export class HeaderList extends PropertyList { - constructor( - parent: PropertyList | undefined, - populate: T[] - ) { - super( - Header, - undefined, - populate - ); - this.parent = parent; - } + constructor(parent: PropertyList | undefined, populate: T[]) { + super(Header, undefined, populate); + this.parent = parent; + } - static isHeaderList(obj: any) { - return '_kind' in obj && obj._kind === 'HeaderList'; - } + static isHeaderList(obj: any) { + return '_kind' in obj && obj._kind === 'HeaderList'; + } - contentSize(): number { - return this.list - .map(header => header.toString()) - .map(headerStr => headerStr.length) // TODO: handle special characters - .reduce((totalSize, headerSize) => totalSize + headerSize, 0); - } + contentSize(): number { + return this.list + .map(header => header.toString()) + .map(headerStr => headerStr.length) // TODO: handle special characters + .reduce((totalSize, headerSize) => totalSize + headerSize, 0); + } } diff --git a/packages/insomnia-scripting-environment/src/objects/insomnia.ts b/packages/insomnia-scripting-environment/src/objects/insomnia.ts index 821f2a3116..611360103f 100644 --- a/packages/insomnia-scripting-environment/src/objects/insomnia.ts +++ b/packages/insomnia-scripting-environment/src/objects/insomnia.ts @@ -22,260 +22,258 @@ import { toUrlObject } from './urls'; import { checkIfUrlIncludesTag } from './utils'; export class InsomniaObject { - public environment: Environment; - public collectionVariables: Environment; - public baseEnvironment: Environment; - public variables: Variables; - public request: ScriptRequest; - public cookies: CookieObject; - public info: RequestInfo; - public response?: ScriptResponse; - public execution: Execution; - public vault?: Vault; + public environment: Environment; + public collectionVariables: Environment; + public baseEnvironment: Environment; + public variables: Variables; + public request: ScriptRequest; + public cookies: CookieObject; + public info: RequestInfo; + public response?: ScriptResponse; + public execution: Execution; + public vault?: Vault; - public clientCertificates: ClientCertificate[]; - private _expect = expect; - private _test = test; - private _skip = skip; + public clientCertificates: ClientCertificate[]; + private _expect = expect; + private _test = test; + private _skip = skip; - private iterationData: Environment; - // TODO: follows will be enabled after Insomnia supports them - private globals: Environment; - private _settings: Settings; + private iterationData: Environment; + // TODO: follows will be enabled after Insomnia supports them + private globals: Environment; + private _settings: Settings; - private requestTestResults: RequestTestResult[]; + private requestTestResults: RequestTestResult[]; - private parentFolders: ParentFolders; + private parentFolders: ParentFolders; - constructor( - rawObj: { - globals: Environment; - iterationData: Environment; - environment: Environment; - baseEnvironment: Environment; - variables: Variables; - request: ScriptRequest; - settings: Settings; - clientCertificates: ClientCertificate[]; - cookies: CookieObject; - requestInfo: RequestInfo; - execution: Execution; - response?: ScriptResponse; - parentFolders: ParentFolders; - vault?: Vault; - }, - ) { - this.globals = rawObj.globals; - this.environment = rawObj.environment; - this.baseEnvironment = rawObj.baseEnvironment; - this.collectionVariables = this.baseEnvironment; // collectionVariables is mapped to baseEnvironment - this.iterationData = rawObj.iterationData; - this.variables = rawObj.variables; - this.cookies = rawObj.cookies; - this.response = rawObj.response; - this.execution = rawObj.execution; - this.vault = rawObj.vault; + constructor(rawObj: { + globals: Environment; + iterationData: Environment; + environment: Environment; + baseEnvironment: Environment; + variables: Variables; + request: ScriptRequest; + settings: Settings; + clientCertificates: ClientCertificate[]; + cookies: CookieObject; + requestInfo: RequestInfo; + execution: Execution; + response?: ScriptResponse; + parentFolders: ParentFolders; + vault?: Vault; + }) { + this.globals = rawObj.globals; + this.environment = rawObj.environment; + this.baseEnvironment = rawObj.baseEnvironment; + this.collectionVariables = this.baseEnvironment; // collectionVariables is mapped to baseEnvironment + this.iterationData = rawObj.iterationData; + this.variables = rawObj.variables; + this.cookies = rawObj.cookies; + this.response = rawObj.response; + this.execution = rawObj.execution; + this.vault = rawObj.vault; - this.info = rawObj.requestInfo; - this.request = rawObj.request; - this._settings = rawObj.settings; - this.clientCertificates = rawObj.clientCertificates; + this.info = rawObj.requestInfo; + this.request = rawObj.request; + this._settings = rawObj.settings; + this.clientCertificates = rawObj.clientCertificates; - this.requestTestResults = new Array(); - this.parentFolders = rawObj.parentFolders; + this.requestTestResults = new Array(); + this.parentFolders = rawObj.parentFolders; - return new Proxy(this, { - get: (target, prop, receiver) => { - if (prop === 'test') { - const testHandler: TestHandler = async (msg: string, fn: () => Promise) => { - await this._test(msg, fn, this.pushRequestTestResult); - }; - testHandler.skip = async (msg: string, fn: () => Promise) => { - await this._skip(msg, fn, this.pushRequestTestResult); - }; + return new Proxy(this, { + get: (target, prop, receiver) => { + if (prop === 'test') { + const testHandler: TestHandler = async (msg: string, fn: () => Promise) => { + await this._test(msg, fn, this.pushRequestTestResult); + }; + testHandler.skip = async (msg: string, fn: () => Promise) => { + await this._skip(msg, fn, this.pushRequestTestResult); + }; - return testHandler; - } - return Reflect.get(target, prop, receiver); - }, - }); - } + return testHandler; + } + return Reflect.get(target, prop, receiver); + }, + }); + } - sendRequest( - request: string | ScriptRequest, - cb: (error?: string, response?: ScriptResponse) => void - ) { - return sendRequest(request, cb, this._settings); - } + sendRequest(request: string | ScriptRequest, cb: (error?: string, response?: ScriptResponse) => void) { + return sendRequest(request, cb, this._settings); + } - test = () => { - // this method is intercepted by the proxy above - }; - - private pushRequestTestResult = (testResult: RequestTestResult) => { - this.requestTestResults = [...this.requestTestResults, testResult]; - }; - - expect = (exp: boolean | number | string | object) => { - return this._expect(exp); - }; - - get settings() { - return undefined; - } - - toObject = () => { - return { - globals: this.globals.toObject(), - environment: this.environment.toObject(), - baseEnvironment: this.baseEnvironment.toObject(), - iterationData: this.iterationData.toObject(), - variables: this.variables.localVarsToObject(), - request: this.request, - settings: this.settings, - clientCertificates: this.clientCertificates, - cookieJar: this.cookies.jar().toInsomniaCookieJar(), - info: this.info.toObject(), - response: this.response ? this.response.toObject() : undefined, - requestTestResults: this.requestTestResults, - execution: this.execution.toObject(), - parentFolders: this.parentFolders.toObject(), - }; + test = () => { + // this method is intercepted by the proxy above + }; + + private pushRequestTestResult = (testResult: RequestTestResult) => { + this.requestTestResults = [...this.requestTestResults, testResult]; + }; + + expect = (exp: boolean | number | string | object) => { + return this._expect(exp); + }; + + get settings() { + return undefined; + } + + toObject = () => { + return { + globals: this.globals.toObject(), + environment: this.environment.toObject(), + baseEnvironment: this.baseEnvironment.toObject(), + iterationData: this.iterationData.toObject(), + variables: this.variables.localVarsToObject(), + request: this.request, + settings: this.settings, + clientCertificates: this.clientCertificates, + cookieJar: this.cookies.jar().toInsomniaCookieJar(), + info: this.info.toObject(), + response: this.response ? this.response.toObject() : undefined, + requestTestResults: this.requestTestResults, + execution: this.execution.toObject(), + parentFolders: this.parentFolders.toObject(), }; + }; } -export async function initInsomniaObject( - rawObj: RequestContext, - log: (...args: any[]) => void, -) { - // Mapping rule for the global environment: - // - when one global environment is selected, `globals` points to the selected one - // Potential mapping rule for the future: - // - The base global environment could also be introduced - const globals = new Environment('globals', rawObj.globals || {}); // could be undefined - // Mapping rule for the environment and base environment: - // - If base environment is selected, both `baseEnvironment` and `environment` point to the selected one. - // - If one sub environment is selected, `baseEnvironment` points to the base env and `environment` points to the selected one. - const baseEnvironment = new Environment(rawObj.baseEnvironment.name || '', rawObj.baseEnvironment.data); - // reuse baseEnvironment when the "selected envrionment" points to the base environment - const environment = rawObj.baseEnvironment.id === rawObj.environment.id ? - baseEnvironment : - new Environment(rawObj.environment.name || '', rawObj.environment.data); - if (rawObj.baseEnvironment.id === rawObj.environment.id) { - log('warning: No environment is selected, modification of insomnia.environment will be applied to the base environment.'); - } - // Mapping rule for the environment user uploaded in collection runner - const iterationData = rawObj.iterationData ? - new Environment(rawObj.iterationData.name, rawObj.iterationData.data) : new Environment('iterationData', {}); - const localVariables = rawObj.transientVariables ? - new Environment(rawObj.transientVariables.name, rawObj.transientVariables.data) : new Environment('transientVariables', {}); - const enableVaultInScripts = rawObj.settings?.enableVaultInScripts || false; - const vault = rawObj.vault ? - new Vault('vault', rawObj.vault, enableVaultInScripts) : new Vault('vault', {}, enableVaultInScripts); - const cookies = new CookieObject(rawObj.cookieJar); - // TODO: update follows when post-request script and iterationData are introduced - const requestInfo = new RequestInfo({ - eventName: rawObj.requestInfo.eventName || 'prerequest', - iteration: rawObj.requestInfo.iteration || 1, - iterationCount: rawObj.requestInfo.iterationCount || 0, - requestName: rawObj.request.name, - requestId: rawObj.request._id, - }); - - const parentFolders = new ParentFolders(rawObj.parentFolders.map(folderObj => - new Folder( - folderObj.id, - folderObj.name, - folderObj.environment, - ) - )); - - const variables = new Variables({ - globalVars: globals, - environmentVars: environment, - collectionVars: baseEnvironment, - iterationDataVars: iterationData, - folderLevelVars: parentFolders.getEnvironments(), - localVars: localVariables, - }); - - // todo: find if theres a better way to get the best cert - // (╯°□°)╯︵ ┻━┻ - const ifUrlIncludesTag = checkIfUrlIncludesTag(rawObj.request.url); - const matchedCertificates = filterClientCertificates(rawObj.clientCertificates || [], rawObj.request.url); - const initEmptyCert = ifUrlIncludesTag || matchedCertificates?.length === 0; - if (initEmptyCert) { - getExistingConsole().warn('The URL contains tags or no matched certificate found, insomnia.request.certificate is initialized as an empty certificate.'); - } - const defaultCertificate = initEmptyCert ? - { - disabled: false, - name: 'Default Certificate', - matches: [], - key: undefined, - cert: undefined, - passphrase: undefined, - pfx: undefined, - } : { - disabled: matchedCertificates[0].disabled, - name: 'The first matched certificate from Settings', - matches: [matchedCertificates[0].host], - key: { src: matchedCertificates[0].key || '' }, - cert: { src: matchedCertificates[0].cert || '' }, - passphrase: matchedCertificates[0].passphrase || undefined, - pfx: { src: matchedCertificates[0].pfx || '' }, // PFX or PKCS12 Certificate - }; - - const proxy = transformToSdkProxyOptions( - rawObj.settings.httpProxy, - rawObj.settings.httpsProxy, - rawObj.settings.proxyEnabled, - rawObj.settings.noProxy, +export async function initInsomniaObject(rawObj: RequestContext, log: (...args: any[]) => void) { + // Mapping rule for the global environment: + // - when one global environment is selected, `globals` points to the selected one + // Potential mapping rule for the future: + // - The base global environment could also be introduced + const globals = new Environment('globals', rawObj.globals || {}); // could be undefined + // Mapping rule for the environment and base environment: + // - If base environment is selected, both `baseEnvironment` and `environment` point to the selected one. + // - If one sub environment is selected, `baseEnvironment` points to the base env and `environment` points to the selected one. + const baseEnvironment = new Environment(rawObj.baseEnvironment.name || '', rawObj.baseEnvironment.data); + // reuse baseEnvironment when the "selected envrionment" points to the base environment + const environment = + rawObj.baseEnvironment.id === rawObj.environment.id + ? baseEnvironment + : new Environment(rawObj.environment.name || '', rawObj.environment.data); + if (rawObj.baseEnvironment.id === rawObj.environment.id) { + log( + 'warning: No environment is selected, modification of insomnia.environment will be applied to the base environment.', ); + } + // Mapping rule for the environment user uploaded in collection runner + const iterationData = rawObj.iterationData + ? new Environment(rawObj.iterationData.name, rawObj.iterationData.data) + : new Environment('iterationData', {}); + const localVariables = rawObj.transientVariables + ? new Environment(rawObj.transientVariables.name, rawObj.transientVariables.data) + : new Environment('transientVariables', {}); + const enableVaultInScripts = rawObj.settings?.enableVaultInScripts || false; + const vault = rawObj.vault + ? new Vault('vault', rawObj.vault, enableVaultInScripts) + : new Vault('vault', {}, enableVaultInScripts); + const cookies = new CookieObject(rawObj.cookieJar); + // TODO: update follows when post-request script and iterationData are introduced + const requestInfo = new RequestInfo({ + eventName: rawObj.requestInfo.eventName || 'prerequest', + iteration: rawObj.requestInfo.iteration || 1, + iterationCount: rawObj.requestInfo.iterationCount || 0, + requestName: rawObj.request.name, + requestId: rawObj.request._id, + }); - const reqUrl = toUrlObject(rawObj.request.url); - reqUrl.addQueryParams( - rawObj.request.parameters - .map(param => ({ key: param.name, value: param.value, disabled: param.disabled })) + const parentFolders = new ParentFolders( + rawObj.parentFolders.map(folderObj => new Folder(folderObj.id, folderObj.name, folderObj.environment)), + ); + + const variables = new Variables({ + globalVars: globals, + environmentVars: environment, + collectionVars: baseEnvironment, + iterationDataVars: iterationData, + folderLevelVars: parentFolders.getEnvironments(), + localVars: localVariables, + }); + + // todo: find if theres a better way to get the best cert + // (╯°□°)╯︵ ┻━┻ + const ifUrlIncludesTag = checkIfUrlIncludesTag(rawObj.request.url); + const matchedCertificates = filterClientCertificates(rawObj.clientCertificates || [], rawObj.request.url); + const initEmptyCert = ifUrlIncludesTag || matchedCertificates?.length === 0; + if (initEmptyCert) { + getExistingConsole().warn( + 'The URL contains tags or no matched certificate found, insomnia.request.certificate is initialized as an empty certificate.', ); + } + const defaultCertificate = initEmptyCert + ? { + disabled: false, + name: 'Default Certificate', + matches: [], + key: undefined, + cert: undefined, + passphrase: undefined, + pfx: undefined, + } + : { + disabled: matchedCertificates[0].disabled, + name: 'The first matched certificate from Settings', + matches: [matchedCertificates[0].host], + key: { src: matchedCertificates[0].key || '' }, + cert: { src: matchedCertificates[0].cert || '' }, + passphrase: matchedCertificates[0].passphrase || undefined, + pfx: { src: matchedCertificates[0].pfx || '' }, // PFX or PKCS12 Certificate + }; - const reqOpt: RequestOptions = { - name: rawObj.request.name, - url: reqUrl, - method: rawObj.request.method, - header: rawObj.request.headers.map( - (header: RequestHeader) => ({ key: header.name, value: header.value, disabled: header.disabled }) - ), - body: toScriptRequestBody(rawObj.request.body), - auth: toPreRequestAuth(rawObj.request.authentication), - proxy, - certificate: defaultCertificate, - pathParameters: rawObj.request.pathParameters, - }; - const request = new ScriptRequest(reqOpt); - const execution = new Execution({ - location: rawObj.execution.location, - skipRequest: rawObj.execution.skipRequest, - nextRequestIdOrName: rawObj.execution.nextRequestIdOrName, - }); + const proxy = transformToSdkProxyOptions( + rawObj.settings.httpProxy, + rawObj.settings.httpsProxy, + rawObj.settings.proxyEnabled, + rawObj.settings.noProxy, + ); - const responseBody = await readBodyFromPath(rawObj.response); - const response = rawObj.response ? toScriptResponse(request, rawObj.response, responseBody) : undefined; + const reqUrl = toUrlObject(rawObj.request.url); + reqUrl.addQueryParams( + rawObj.request.parameters.map(param => ({ key: param.name, value: param.value, disabled: param.disabled })), + ); - return new InsomniaObject({ - globals, - environment, - baseEnvironment, - iterationData, - vault, - variables, - request, - settings: rawObj.settings, - clientCertificates: rawObj.clientCertificates, - cookies, - requestInfo, - response, - execution, - parentFolders, - }); -}; + const reqOpt: RequestOptions = { + name: rawObj.request.name, + url: reqUrl, + method: rawObj.request.method, + header: rawObj.request.headers.map((header: RequestHeader) => ({ + key: header.name, + value: header.value, + disabled: header.disabled, + })), + body: toScriptRequestBody(rawObj.request.body), + auth: toPreRequestAuth(rawObj.request.authentication), + proxy, + certificate: defaultCertificate, + pathParameters: rawObj.request.pathParameters, + }; + const request = new ScriptRequest(reqOpt); + const execution = new Execution({ + location: rawObj.execution.location, + skipRequest: rawObj.execution.skipRequest, + nextRequestIdOrName: rawObj.execution.nextRequestIdOrName, + }); + + const responseBody = await readBodyFromPath(rawObj.response); + const response = rawObj.response ? toScriptResponse(request, rawObj.response, responseBody) : undefined; + + return new InsomniaObject({ + globals, + environment, + baseEnvironment, + iterationData, + vault, + variables, + request, + settings: rawObj.settings, + clientCertificates: rawObj.clientCertificates, + cookies, + requestInfo, + response, + execution, + parentFolders, + }); +} diff --git a/packages/insomnia-scripting-environment/src/objects/interfaces.ts b/packages/insomnia-scripting-environment/src/objects/interfaces.ts index 913a229a86..ee5ab979da 100644 --- a/packages/insomnia-scripting-environment/src/objects/interfaces.ts +++ b/packages/insomnia-scripting-environment/src/objects/interfaces.ts @@ -9,29 +9,29 @@ import type { RequestInfoOption } from './request-info'; import type { RequestTestResult } from './test'; export interface IEnvironment { - id: string; - name: string; - data: object; + id: string; + name: string; + data: object; } export interface RequestContext { - request: Request; - timelinePath: string; - environment: IEnvironment; - baseEnvironment: IEnvironment; - vault?: IEnvironment; - collectionVariables?: object; - globals?: object; - iterationData?: Omit; - timeout: number; - settings: Settings; - clientCertificates: ClientCertificate[]; - cookieJar: InsomniaCookieJar; - // only for the after-response script - response?: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError; - requestTestResults?: RequestTestResult[]; - requestInfo: RequestInfoOption; - execution: ExecutionOption; - logs: string[]; - transientVariables?: Omit; - parentFolders: { id: string; name: string; environment: Record }[]; + request: Request; + timelinePath: string; + environment: IEnvironment; + baseEnvironment: IEnvironment; + vault?: IEnvironment; + collectionVariables?: object; + globals?: object; + iterationData?: Omit; + timeout: number; + settings: Settings; + clientCertificates: ClientCertificate[]; + cookieJar: InsomniaCookieJar; + // only for the after-response script + response?: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError; + requestTestResults?: RequestTestResult[]; + requestInfo: RequestInfoOption; + execution: ExecutionOption; + logs: string[]; + transientVariables?: Omit; + parentFolders: { id: string; name: string; environment: Record }[]; } diff --git a/packages/insomnia-scripting-environment/src/objects/interpolator.ts b/packages/insomnia-scripting-environment/src/objects/interpolator.ts index 450781395a..7a28c2455b 100644 --- a/packages/insomnia-scripting-environment/src/objects/interpolator.ts +++ b/packages/insomnia-scripting-environment/src/objects/interpolator.ts @@ -2,69 +2,64 @@ import { fakerFunctions } from 'insomnia/src/ui/components/templating/faker-func import { configure, type ConfigureOptions, type Environment as NunjuncksEnv } from 'nunjucks'; class Interpolator { - private engine: NunjuncksEnv; + private engine: NunjuncksEnv; - constructor(config: ConfigureOptions) { - this.engine = configure(config); + constructor(config: ConfigureOptions) { + this.engine = configure(config); + } + + render = (template: string, context: object) => { + // TODO: handle timeout + // TODO: support plugin? + return this.engine.renderString(this.renderWithFaker(template), context); + }; + + renderWithFaker = (template: string) => { + const segments = template.split('}}'); + if (segments.length === 1) { + return template; } - render = (template: string, context: object) => { - // TODO: handle timeout - // TODO: support plugin? - return this.engine.renderString( - this.renderWithFaker(template), - context - ); - }; + const translatedSegments = segments.map(segment => { + const tagStart = segment.lastIndexOf('{{'); + if (tagStart < 0) { + return segment; + } - renderWithFaker = (template: string) => { - const segments = template.split('}}'); - if (segments.length === 1) { - return template; - } + const tagName = segment.slice(tagStart + 2).trim(); + if (!tagName.startsWith('$')) { + // it is a tag probably for interpolating, at least not for generating + return segment + '}}'; + } + const funcName = tagName.slice(1) as keyof typeof fakerFunctions; // remove prefix '$' - const translatedSegments = segments.map(segment => { - const tagStart = segment.lastIndexOf('{{'); - if ((tagStart) < 0) { - return segment; - } + if (!fakerFunctions[funcName]) { + throw Error(`replaceIn: no faker function is found: ${funcName}`); + } - const tagName = segment - .slice(tagStart + 2) - .trim(); - if (!tagName.startsWith('$')) { - // it is a tag probably for interpolating, at least not for generating - return segment + '}}'; - } - const funcName = tagName.slice(1) as keyof typeof fakerFunctions; // remove prefix '$' + const generated = fakerFunctions[funcName](); + return segment.slice(0, tagStart) + generated; + }); - if (!fakerFunctions[funcName]) { - throw Error(`replaceIn: no faker function is found: ${funcName}`); - }; - - const generated = fakerFunctions[funcName](); - return segment.slice(0, tagStart) + generated; - }); - - return translatedSegments.join(''); - }; + return translatedSegments.join(''); + }; } const interpolator = new Interpolator({ - autoescape: false, - // Don't escape HTML - throwOnUndefined: true, - // Strict mode - tags: { - blockStart: '{%', - blockEnd: '%}', - variableStart: '{{', - variableEnd: '}}', - commentStart: '{#', - commentEnd: '#}', - }, + autoescape: false, + // Don't escape HTML + throwOnUndefined: true, + // Strict mode + tags: { + blockStart: '{%', + blockEnd: '%}', + variableStart: '{{', + variableEnd: '}}', + commentStart: '{#', + commentEnd: '#}', + }, }); export function getInterpolator() { - return interpolator; + return interpolator; } diff --git a/packages/insomnia-scripting-environment/src/objects/properties.ts b/packages/insomnia-scripting-environment/src/objects/properties.ts index 8dc4a1d1ff..194ffb7f23 100644 --- a/packages/insomnia-scripting-environment/src/objects/properties.ts +++ b/packages/insomnia-scripting-environment/src/objects/properties.ts @@ -5,429 +5,411 @@ import _ from 'lodash'; import { getInterpolator } from './interpolator'; export const unsupportedError = (featureName: string, alternative?: string) => { - const message = `${featureName} is not supported yet` + - (alternative ? `, please use ${alternative} instead temporarily.` : ''); - return Error(message); + const message = + `${featureName} is not supported yet` + (alternative ? `, please use ${alternative} instead temporarily.` : ''); + return Error(message); }; export class PropertyBase { - public _kind = 'PropertyBase'; - protected _parent: PropertyBase | undefined = undefined; - protected description?: string; + public _kind = 'PropertyBase'; + protected _parent: PropertyBase | undefined = undefined; + protected description?: string; - constructor(description?: string) { - this.description = description; + constructor(description?: string) { + this.description = description; + } + + static propertyIsMeta(_value: any, key: string) { + // no meta is defined in Insomnia and it basically find properties start with '_' + // '_' is also rejected here + return key && key.startsWith('_'); + } + + static propertyUnprefixMeta(_value: any, key: string) { + return _.trimStart(key, '_'); + } + + // TODO: temporarily disable this + // static toJSON(obj: { toJSON: () => string }) { + // return obj.toJSON(); + // } + + meta() { + return {}; + } + + parent() { + return this._parent; + } + + forEachParent(_options: { withRoot?: boolean }, iterator: (obj: PropertyBase) => boolean) { + const currentParent = this.parent(); + if (!currentParent) { + return; } - static propertyIsMeta(_value: any, key: string) { - // no meta is defined in Insomnia and it basically find properties start with '_' - // '_' is also rejected here - return key && key.startsWith('_'); + const queue: PropertyBase[] = [currentParent]; + const parents: PropertyBase[] = []; + + while (queue.length > 0) { + const ancester = queue.shift(); + if (!ancester) { + continue; + } + + // TODO: check options + const cloned = clone(ancester); + const keepIterating = iterator(cloned); + parents.push(cloned); + if (!keepIterating) { + break; + } + + const olderAncester = ancester.parent(); + if (olderAncester) { + queue.push(olderAncester); + } } - static propertyUnprefixMeta(_value: any, key: string) { - return _.trimStart(key, '_'); + return parents; + } + + findInParents(property: string, customizer?: (ancester: PropertyBase) => boolean): PropertyBase | undefined { + const currentParent = this.parent(); + if (!currentParent) { + return; } - // TODO: temporarily disable this - // static toJSON(obj: { toJSON: () => string }) { - // return obj.toJSON(); - // } + const queue: PropertyBase[] = [currentParent]; - meta() { - return {}; - }; + while (queue.length > 0) { + const ancester = queue.shift(); + if (!ancester) { + continue; + } - parent() { - return this._parent; - } - - forEachParent( - _options: { withRoot?: boolean }, - iterator: (obj: PropertyBase) => boolean, - ) { - const currentParent = this.parent(); - if (!currentParent) { - return; + const cloned = clone(ancester); + const hasProperty = Object.keys(cloned.meta()).includes(property); + if (!hasProperty) { + // keep traversing until parent has the property + // no op + } else { + if (customizer) { + if (customizer(cloned)) { + // continue until customizer returns a truthy value + return cloned; + } + } else { + // customizer is not specified + // stop at the first parent that contains the property + return cloned; } + } - const queue: PropertyBase[] = [currentParent]; - const parents: PropertyBase[] = []; - - while (queue.length > 0) { - const ancester = queue.shift(); - if (!ancester) { - continue; - } - - // TODO: check options - const cloned = clone(ancester); - const keepIterating = iterator(cloned); - parents.push(cloned); - if (!keepIterating) { - break; - } - - const olderAncester = ancester.parent(); - if (olderAncester) { - queue.push(olderAncester); - } - } - - return parents; + const olderAncester = ancester.parent(); + if (olderAncester) { + queue.push(olderAncester); + } } - findInParents( - property: string, - customizer?: (ancester: PropertyBase) => boolean, - ): PropertyBase | undefined { - const currentParent = this.parent(); - if (!currentParent) { - return; - } + return undefined; + } - const queue: PropertyBase[] = [currentParent]; + toJSON() { + const entriesToExport = Object.entries(this).filter( + (kv: [string, any]) => typeof kv[1] !== 'function' && typeof kv[1] !== 'undefined' && kv[0] !== '_kind', + ); - while (queue.length > 0) { - const ancester = queue.shift(); - if (!ancester) { - continue; - } + return Object.fromEntries(entriesToExport); + } - const cloned = clone(ancester); - const hasProperty = Object.keys(cloned.meta()).includes(property); - if (!hasProperty) { - // keep traversing until parent has the property - // no op - } else { - if (customizer) { - if (customizer(cloned)) { - // continue until customizer returns a truthy value - return cloned; - } - } else { - // customizer is not specified - // stop at the first parent that contains the property - return cloned; - } - } + toObject() { + return this.toJSON(); + } - const olderAncester = ancester.parent(); - if (olderAncester) { - queue.push(olderAncester); - } - } - - return undefined; - } - - toJSON() { - const entriesToExport = Object - .entries(this) - .filter((kv: [string, any]) => - typeof kv[1] !== 'function' - && typeof kv[1] !== 'undefined' - && kv[0] !== '_kind' - ); - - return Object.fromEntries(entriesToExport); - } - - toObject() { - return this.toJSON(); - } - - toString() { - return JSON.stringify(this.toJSON()); - } + toString() { + return JSON.stringify(this.toJSON()); + } } export class Property extends PropertyBase { - id: string; - name?: string; - disabled?: boolean; - // TODO: parent property will be introduced when collection manipulation is supported + id: string; + name?: string; + disabled?: boolean; + // TODO: parent property will be introduced when collection manipulation is supported - constructor( - id?: string, - name?: string, - disabled?: boolean, - info?: { id?: string; name?: string }, - ) { - super(); - this._kind = 'Property'; - this.id = info?.id || id || ''; - this.name = info?.name || name || ''; - this.disabled = disabled || false; + constructor(id?: string, name?: string, disabled?: boolean, info?: { id?: string; name?: string }) { + super(); + this._kind = 'Property'; + this.id = info?.id || id || ''; + this.name = info?.name || name || ''; + this.disabled = disabled || false; + } + static _index = 'id'; + + static replaceSubstitutions(content: string, ...variables: object[]): string { + if (!Array.isArray(variables) || typeof content !== 'string') { + throw Error("replaceSubstitutions: the first param's type is not string or other parameters are not an array"); } - static _index = 'id'; + let context: object = {}; + // the searching priority of rendering is from left to right + variables.reverse().forEach(variable => (context = { ...context, ...variable })); - static replaceSubstitutions(content: string, ...variables: object[]): string { - if (!Array.isArray(variables) || typeof content !== 'string') { - throw Error("replaceSubstitutions: the first param's type is not string or other parameters are not an array"); - } + return getInterpolator().render(content, context); + } - let context: object = {}; - // the searching priority of rendering is from left to right - variables.reverse().forEach(variable => context = { ...context, ...variable }); - - return getInterpolator().render(content, context); + static replaceSubstitutionsIn(obj: object, ...variables: object[]): object { + if (!Array.isArray(variables) || typeof obj !== 'object') { + throw Error("replaceSubstitutions: the first param's type is not object or other parameters are not an array"); } - static replaceSubstitutionsIn(obj: object, ...variables: object[]): object { - if (!Array.isArray(variables) || typeof obj !== 'object') { - throw Error("replaceSubstitutions: the first param's type is not object or other parameters are not an array"); - } + try { + const content = JSON.stringify(obj); - try { - const content = JSON.stringify(obj); + let context: object = {}; + // the searching priority of rendering is from left to right + variables.reverse().forEach(variable => { + context = { ...context, ...variable }; + }); - let context: object = {}; - // the searching priority of rendering is from left to right - variables.reverse().forEach(variable => { - context = { ...context, ...variable }; - }); - - const rendered = getInterpolator().render(content, context); - return JSON.parse(rendered); - - } catch (e: any) { - throw Error(`replaceSubstitutionsIn: ${e.toString()}`); - } + const rendered = getInterpolator().render(content, context); + return JSON.parse(rendered); + } catch (e: any) { + throw Error(`replaceSubstitutionsIn: ${e.toString()}`); } + } - describe(content: string, typeName: string) { - this._kind = typeName; - this.description = content; - } + describe(content: string, typeName: string) { + this._kind = typeName; + this.description = content; + } } export class PropertyList { - protected _kind = 'PropertyList'; - protected list: T[] = []; + protected _kind = 'PropertyList'; + protected list: T[] = []; - constructor( - protected typeClass: { _index?: string }, - protected parent: Property | PropertyList | undefined, - populate: T[], - ) { - this.parent = parent; - this.list = populate; + constructor( + protected typeClass: { _index?: string }, + protected parent: Property | PropertyList | undefined, + populate: T[], + ) { + this.parent = parent; + this.list = populate; + } + + static isPropertyList(obj: object) { + return '_kind' in obj && obj._kind === 'PropertyList'; + } + + add(item: T) { + this.list.push(item); + } + + all() { + return this.list.map(pp => pp.toJSON()); + } + + append(item: T) { + this.add(item); + } + + assimilate(source: T[] | PropertyList, prune?: boolean) { + // it doesn't update values from a source list + if (prune) { + this.clear(); + } + if ('list' in source) { + // it is PropertyList + this.list.push(...source.list); + } else { + this.list.push(...source); + } + } + + clear() { + this.list = []; + } + + count() { + return this.list.length; + } + + each(iterator: (item: T) => void, context: object) { + interface Iterator { + context?: object; + (item: T): void; + } + const it: Iterator = iterator; + it.context = context; + + this.list.forEach(it); + } + + // TODO: unsupported yet as properties are not organized as hierarchy + + eachParent(_iterator: (parent: Property, prev: Property) => void, _context?: object) { + throw unsupportedError('eachParent'); + } + + filter(rule: (item: T) => boolean, context: object) { + interface Iterator { + context?: object; + (item: T): boolean; + } + const it: Iterator = rule; + it.context = context; + + return this.list.filter(it); + } + + // TODO: support returning {Item|ItemGroup} + find(rule: (item: T) => boolean, context?: object) { + interface Finder { + context?: object; + (item: T): boolean; + } + const finder: Finder = rule; + finder.context = context; + + return this.list.find(finder); + } + + // it does not return underlying type of the item because they are not supported + get(key: string) { + return this.one(key); + } + + // TODO: value is not used as its usage is unknown + + has(item: T, _value?: any) { + return this.indexOf(item) >= 0; + } + + idx(index: number) { + if (index <= this.list.length - 1) { + return this.list[index]; + } + return undefined; + } + + indexOf(item: string | T) { + const indexFieldName = this.typeClass._index || 'id'; + + for (let i = 0; i < this.list.length; i++) { + const record = this.list[i] as Record; + + if (typeof item === 'string' && record[indexFieldName] === item) { + return i; + } + const itemRecord = item as Record; + if (record[indexFieldName] === itemRecord[indexFieldName]) { + return i; + } + } + return -1; + } + + insert(item: T, before?: number) { + if (before != null && before >= 0 && before <= this.list.length - 1) { + this.list = [...this.list.slice(0, before), item, ...this.list.slice(before)]; + } else { + this.append(item); + } + } + + insertAfter(item: T, after?: number) { + if (after != null && after >= 0 && after <= this.list.length - 1) { + this.list = [...this.list.slice(0, after + 1), item, ...this.list.slice(after + 1)]; + } else { + this.append(item); + } + } + + map(iterator: (item: T) => any, context: object) { + interface Iterator { + context?: object; + (item: T): any; + } + const it: Iterator = iterator; + it.context = context; + + return this.list.map(it); + } + + one(id: string) { + const indexFieldName = this.typeClass._index || 'id'; + + for (let i = this.list.length - 1; i >= 0; i--) { + const record = this.list[i] as Record; + if (record[indexFieldName] === id) { + return this.list[i]; + } } - static isPropertyList(obj: object) { - return '_kind' in obj && obj._kind === 'PropertyList'; + return undefined; + } + + populate(items: T[]) { + this.list = [...this.list, ...items]; + } + + prepend(item: T) { + this.list = [item, ...this.list]; + } + + reduce(iterator: (acc: any, item: T) => any, accumulator: any, context: object) { + interface Iterator { + context?: object; + (acc: any, item: T): any; + } + const it: Iterator = iterator; + it.context = context; + + return this.list.reduce(it, accumulator); + } + + remove(predicate: T | ((item: T) => boolean), context: object) { + if (typeof predicate === 'function') { + const reversePredicate = (item: T) => !predicate(item); + this.list = this.filter(reversePredicate, context); + } else { + this.list = this.filter(item => !equal(predicate, item), context); + } + } + + repopulate(items: T[]) { + this.clear(); + this.populate(items); + } + + // TODO: unsupported yet + + toObject(_excludeDisabled?: boolean, _caseSensitive?: boolean, _multiValue?: boolean, _sanitizeKeys?: boolean) { + // it just dump all properties of each element without arguments + // then user is able to handle them by themself + return this.list.map(elem => elem.toJSON()); + } + + toString() { + const itemStrs = this.list.map(item => item.toString()); + return `[${itemStrs.join('; ')}]`; + } + + upsert(item: T): boolean { + if (item == null) { + return false; } - add(item: T) { - this.list.push(item); + const itemIdx = this.indexOf(item); + if (itemIdx >= 0) { + this.list = [...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)]; + return false; } - all() { - return this.list.map(pp => pp.toJSON()); - } - - append(item: T) { - this.add(item); - } - - assimilate(source: T[] | PropertyList, prune?: boolean) { - // it doesn't update values from a source list - if (prune) { - this.clear(); - } - if ('list' in source) { // it is PropertyList - this.list.push(...source.list); - } else { - this.list.push(...source); - } - } - - clear() { - this.list = []; - } - - count() { - return this.list.length; - } - - each(iterator: (item: T) => void, context: object) { - interface Iterator { - context?: object; - (item: T): void; - } - const it: Iterator = iterator; - it.context = context; - - this.list.forEach(it); - } - - // TODO: unsupported yet as properties are not organized as hierarchy - - eachParent(_iterator: (parent: Property, prev: Property) => void, _context?: object) { - throw unsupportedError('eachParent'); - } - - filter(rule: (item: T) => boolean, context: object) { - interface Iterator { - context?: object; - (item: T): boolean; - } - const it: Iterator = rule; - it.context = context; - - return this.list.filter(it); - } - - // TODO: support returning {Item|ItemGroup} - find(rule: (item: T) => boolean, context?: object) { - interface Finder { - context?: object; - (item: T): boolean; - } - const finder: Finder = rule; - finder.context = context; - - return this.list.find(finder); - } - - // it does not return underlying type of the item because they are not supported - get(key: string) { - return this.one(key); - } - - // TODO: value is not used as its usage is unknown - - has(item: T, _value?: any) { - return this.indexOf(item) >= 0; - } - - idx(index: number) { - if (index <= this.list.length - 1) { - return this.list[index]; - } - return undefined; - } - - indexOf(item: string | T) { - const indexFieldName = this.typeClass._index || 'id'; - - for (let i = 0; i < this.list.length; i++) { - const record = this.list[i] as Record; - - if (typeof item === 'string' && record[indexFieldName] === item) { - return i; - } - const itemRecord = item as Record; - if (record[indexFieldName] === itemRecord[indexFieldName]) { - return i; - } - - } - return -1; - } - - insert(item: T, before?: number) { - if (before != null && before >= 0 && before <= this.list.length - 1) { - this.list = [...this.list.slice(0, before), item, ...this.list.slice(before)]; - } else { - this.append(item); - } - } - - insertAfter(item: T, after?: number) { - if (after != null && after >= 0 && after <= this.list.length - 1) { - this.list = [...this.list.slice(0, after + 1), item, ...this.list.slice(after + 1)]; - } else { - this.append(item); - } - } - - map(iterator: (item: T) => any, context: object) { - interface Iterator { - context?: object; - (item: T): any; - } - const it: Iterator = iterator; - it.context = context; - - return this.list.map(it); - } - - one(id: string) { - const indexFieldName = this.typeClass._index || 'id'; - - for (let i = this.list.length - 1; i >= 0; i--) { - - const record = this.list[i] as Record; - if (record[indexFieldName] === id) { - return this.list[i]; - } - } - - return undefined; - } - - populate(items: T[]) { - this.list = [...this.list, ...items]; - } - - prepend(item: T) { - this.list = [item, ...this.list]; - } - - reduce(iterator: ((acc: any, item: T) => any), accumulator: any, context: object) { - interface Iterator { - context?: object; - (acc: any, item: T): any; - } - const it: Iterator = iterator; - it.context = context; - - return this.list.reduce(it, accumulator); - } - - remove(predicate: T | ((item: T) => boolean), context: object) { - if (typeof predicate === 'function') { - const reversePredicate = (item: T) => !predicate(item); - this.list = this.filter(reversePredicate, context); - } else { - this.list = this.filter(item => !equal(predicate, item), context); - } - } - - repopulate(items: T[]) { - this.clear(); - this.populate(items); - } - - // TODO: unsupported yet - - toObject(_excludeDisabled?: boolean, _caseSensitive?: boolean, _multiValue?: boolean, _sanitizeKeys?: boolean) { - // it just dump all properties of each element without arguments - // then user is able to handle them by themself - return this.list.map(elem => elem.toJSON()); - } - - toString() { - const itemStrs = this.list.map(item => item.toString()); - return `[${itemStrs.join('; ')}]`; - } - - upsert(item: T): boolean { - if (item == null) { - return false; - } - - const itemIdx = this.indexOf(item); - if (itemIdx >= 0) { - this.list = [...this.list.splice(0, itemIdx), item, ...this.list.splice(itemIdx + 1)]; - return false; - } - - this.add(item); - return true; - } + this.add(item); + return true; + } } diff --git a/packages/insomnia-scripting-environment/src/objects/proxy-configs.ts b/packages/insomnia-scripting-environment/src/objects/proxy-configs.ts index 5d578cb062..42147df2f1 100644 --- a/packages/insomnia-scripting-environment/src/objects/proxy-configs.ts +++ b/packages/insomnia-scripting-environment/src/objects/proxy-configs.ts @@ -1,9 +1,51 @@ import { getExistingConsole } from './console'; import { Property, PropertyList } from './properties'; -import type { Url} from './urls'; +import type { Url } from './urls'; import { UrlMatchPattern, UrlMatchPatternList } from './urls'; export interface ProxyConfigOptions { + match: string; + host: string; + port?: number; + tunnel: boolean; + disabled?: boolean; + authenticate: boolean; + username: string; + password: string; + // follows are for compatibility with Insomnia + bypass?: string[]; + protocol: string; +} + +export class ProxyConfig extends Property { + override _kind = 'ProxyConfig'; + type: string; + + host: string; + match: string; + port?: number; + tunnel: boolean; + authenticate: boolean; + username: string; + password: string; + bypass: string[]; // it is for compatibility with Insomnia's bypass list + protocol: string; + + static authenticate = false; + static bypass: UrlMatchPatternList = new UrlMatchPatternList(undefined, []); + static host = ''; + static match = ''; + static password = ''; + static port?: number = undefined; + static tunnel = false; // unsupported + static username = ''; + static protocol = 'https:'; + + constructor(def: { + id?: string; + name?: string; + type?: string; + match: string; host: string; port?: number; @@ -12,15 +54,63 @@ export interface ProxyConfigOptions { authenticate: boolean; username: string; password: string; - // follows are for compatibility with Insomnia bypass?: string[]; protocol: string; -} + }) { + super(); -export class ProxyConfig extends Property { - override _kind = 'ProxyConfig'; - type: string; + this.id = def.id ? def.id : ''; + this.name = def.name ? def.name : ''; + this.type = def.type ? def.type : ''; + this.disabled = def.disabled ? def.disabled : false; + this.host = def.host; + this.match = def.match; + this.port = def.port; + this.tunnel = def.tunnel; + this.authenticate = def.authenticate; + this.username = def.username; + this.password = def.password; + this.bypass = def.bypass || []; + this.protocol = def.protocol; + } + + static override _index = 'key'; + + static isProxyConfig(obj: object) { + return '_kind' in obj && obj._kind === 'ProxyConfig'; + } + + getProtocols(): string[] { + // match field example: 'http+https://example.com/*' + const urlMatch = new UrlMatchPattern(this.match); + return urlMatch.getProtocols(); + } + + getProxyUrl(): string { + // http://proxy_username:proxy_password@proxy.com:8080 + const portSegment = this.port === undefined ? '' : `:${this.port}`; + + if (this.authenticate) { + return `${this.protocol}//${this.username}:${this.password}@${this.host}${portSegment}`; + } + return `${this.protocol}//${this.host}${portSegment}`; + } + + test(url?: string) { + if (!url) { + // TODO: it is confusing in which case url arg is optional + return false; + } + if (this.bypass.includes(url)) { + return false; + } + + const urlMatch = new UrlMatchPattern(this.match); + return urlMatch.test(url); + } + + update(options: { host: string; match: string; port?: number; @@ -28,112 +118,20 @@ export class ProxyConfig extends Property { authenticate: boolean; username: string; password: string; - bypass: string[]; // it is for compatibility with Insomnia's bypass list - protocol: string; + }) { + this.host = options.host; + this.match = options.match; + this.port = options.port; + this.tunnel = options.tunnel; + this.authenticate = options.authenticate; + this.username = options.username; + this.password = options.password; + } - static authenticate = false; - static bypass: UrlMatchPatternList = new UrlMatchPatternList(undefined, []); - static host = ''; - static match = ''; - static password = ''; - static port?: number = undefined; - static tunnel = false; // unsupported - static username = ''; - static protocol = 'https:'; - - constructor(def: { - id?: string; - name?: string; - type?: string; - - match: string; - host: string; - port?: number; - tunnel: boolean; - disabled?: boolean; - authenticate: boolean; - username: string; - password: string; - bypass?: string[]; - protocol: string; - }) { - super(); - - this.id = def.id ? def.id : ''; - this.name = def.name ? def.name : ''; - this.type = def.type ? def.type : ''; - this.disabled = def.disabled ? def.disabled : false; - - this.host = def.host; - this.match = def.match; - this.port = def.port; - this.tunnel = def.tunnel; - this.authenticate = def.authenticate; - this.username = def.username; - this.password = def.password; - this.bypass = def.bypass || []; - this.protocol = def.protocol; - } - - static override _index = 'key'; - - static isProxyConfig(obj: object) { - return '_kind' in obj && obj._kind === 'ProxyConfig'; - } - - getProtocols(): string[] { - // match field example: 'http+https://example.com/*' - const urlMatch = new UrlMatchPattern(this.match); - return urlMatch.getProtocols(); - } - - getProxyUrl(): string { - // http://proxy_username:proxy_password@proxy.com:8080 - const portSegment = this.port === undefined ? '' : `:${this.port}`; - - if (this.authenticate) { - return `${this.protocol}//${this.username}:${this.password}@${this.host}${portSegment}`; - } - return `${this.protocol}//${this.host}${portSegment}`; - - } - - test(url?: string) { - if (!url) { - // TODO: it is confusing in which case url arg is optional - return false; - } - if (this.bypass.includes(url)) { - return false; - } - - const urlMatch = new UrlMatchPattern(this.match); - return urlMatch.test(url); - } - - update(options: { - host: string; - match: string; - port?: number; - tunnel: boolean; - authenticate: boolean; - username: string; - password: string; - }) { - this.host = options.host; - this.match = options.match; - this.port = options.port; - this.tunnel = options.tunnel; - this.authenticate = options.authenticate; - this.username = options.username; - this.password = options.password; - } - - - updateProtocols(_protocols: string[]) { - // In Insomnia there is no whitelist while there is a blacklist - throw Error('updateProtocols is not supported in Insomnia'); - } + updateProtocols(_protocols: string[]) { + // In Insomnia there is no whitelist while there is a blacklist + throw Error('updateProtocols is not supported in Insomnia'); + } } // example: @@ -143,89 +141,81 @@ export class ProxyConfig extends Property { // ]); export class ProxyConfigList extends PropertyList { - constructor(parent: PropertyList | undefined, populate: T[]) { - super( - ProxyConfig, - undefined, - populate - ); - this.parent = parent; + constructor(parent: PropertyList | undefined, populate: T[]) { + super(ProxyConfig, undefined, populate); + this.parent = parent; + } + + static isProxyConfigList(obj: any) { + return '_kind' in obj && obj._kind === 'ProxyConfigList'; + } + + resolve(url?: Url) { + if (!url) { + return null; } - static isProxyConfigList(obj: any) { - return '_kind' in obj && obj._kind === 'ProxyConfigList'; - } - - resolve(url?: Url) { - if (!url) { - return null; - } - - const urlStr = url.toString(); - const matches = this.list - .filter((proxyConfig: ProxyConfig) => { - return proxyConfig.test(urlStr); - }) - .map(proxyConfig => proxyConfig.toJSON()); - - if (matches.length > 0) { - return matches[0]; - } - return null; + const urlStr = url.toString(); + const matches = this.list + .filter((proxyConfig: ProxyConfig) => { + return proxyConfig.test(urlStr); + }) + .map(proxyConfig => proxyConfig.toJSON()); + + if (matches.length > 0) { + return matches[0]; } + return null; + } } export function transformToSdkProxyOptions( - httpProxy: string, - httpsProxy: string, - proxyEnabled: boolean, - noProxy: string, + httpProxy: string, + httpsProxy: string, + proxyEnabled: boolean, + noProxy: string, ) { - const bestProxy = httpsProxy || httpProxy || ''; - const enabledProxy = proxyEnabled && bestProxy.trim() !== ''; - const bypassProxyList = noProxy ? - noProxy - .split(',') - .map(urlStr => urlStr.trim()) : - []; - const proxy: ProxyConfigOptions = { - disabled: !enabledProxy, - match: '', - bypass: bypassProxyList, - host: '', - port: undefined, - tunnel: false, - authenticate: false, - username: '', - password: '', - protocol: 'http', - }; + const bestProxy = httpsProxy || httpProxy || ''; + const enabledProxy = proxyEnabled && bestProxy.trim() !== ''; + const bypassProxyList = noProxy ? noProxy.split(',').map(urlStr => urlStr.trim()) : []; + const proxy: ProxyConfigOptions = { + disabled: !enabledProxy, + match: '', + bypass: bypassProxyList, + host: '', + port: undefined, + tunnel: false, + authenticate: false, + username: '', + password: '', + protocol: 'http', + }; - if (bestProxy !== '') { - let sanitizedProxy = bestProxy; - if (bestProxy.indexOf('://') === -1) { - getExistingConsole().warn(`The protocol is missing for proxy, 'https:' is enabled for: ${bestProxy}`); - sanitizedProxy = 'https://' + bestProxy; - } - - try { - const sanitizedProxyUrlOptions = new URL(sanitizedProxy); // it should just work in node and browser - - if (sanitizedProxyUrlOptions.port !== '') { - proxy.port = parseInt(sanitizedProxyUrlOptions.port, 10); - } - - proxy.protocol = sanitizedProxyUrlOptions.protocol; - proxy.host = sanitizedProxyUrlOptions.hostname; - proxy.username = sanitizedProxyUrlOptions.username; - proxy.password = sanitizedProxyUrlOptions.password; - if (proxy.username || proxy.password) { - proxy.authenticate = true; - } - } catch (e) { - throw `Failed to parse proxy (${sanitizedProxy}): ${e.message}`; - } + if (bestProxy !== '') { + let sanitizedProxy = bestProxy; + if (bestProxy.indexOf('://') === -1) { + getExistingConsole().warn(`The protocol is missing for proxy, 'https:' is enabled for: ${bestProxy}`); + sanitizedProxy = 'https://' + bestProxy; } - return proxy; + try { + const sanitizedProxyUrlOptions = new URL(sanitizedProxy); // it should just work in node and browser + + if (sanitizedProxyUrlOptions.port !== '') { + proxy.port = parseInt(sanitizedProxyUrlOptions.port, 10); + } + + proxy.protocol = sanitizedProxyUrlOptions.protocol; + proxy.host = sanitizedProxyUrlOptions.hostname; + proxy.username = sanitizedProxyUrlOptions.username; + proxy.password = sanitizedProxyUrlOptions.password; + if (proxy.username || proxy.password) { + proxy.authenticate = true; + } + } catch (e) { + throw `Failed to parse proxy (${sanitizedProxy}): ${e.message}`; + } + } + + return proxy; } diff --git a/packages/insomnia-scripting-environment/src/objects/request-info.ts b/packages/insomnia-scripting-environment/src/objects/request-info.ts index 9faad2b01c..66b7e3d61d 100644 --- a/packages/insomnia-scripting-environment/src/objects/request-info.ts +++ b/packages/insomnia-scripting-environment/src/objects/request-info.ts @@ -1,35 +1,35 @@ export type EventName = 'prerequest' | 'test'; export interface RequestInfoOption { - eventName?: EventName; - iteration?: number; - iterationCount?: number; - requestName?: string; - requestId?: string; -}; + eventName?: EventName; + iteration?: number; + iterationCount?: number; + requestName?: string; + requestId?: string; +} export class RequestInfo { - public eventName: EventName; - public iteration: number; - public iterationCount: number; - public requestName: string; - public requestId: string; + public eventName: EventName; + public iteration: number; + public iterationCount: number; + public requestName: string; + public requestId: string; - constructor(options: RequestInfoOption) { - this.eventName = options.eventName || 'prerequest'; - this.iteration = options.iteration || 1; - this.iterationCount = options.iterationCount || 1; - this.requestName = options.requestName || ''; - this.requestId = options.requestId || ''; - } + constructor(options: RequestInfoOption) { + this.eventName = options.eventName || 'prerequest'; + this.iteration = options.iteration || 1; + this.iterationCount = options.iterationCount || 1; + this.requestName = options.requestName || ''; + this.requestId = options.requestId || ''; + } - toObject = () => { - return { - eventName: this.eventName, - iteration: this.iteration, - iterationCount: this.iterationCount, - requestName: this.requestName, - requestId: this.requestId, - }; + toObject = () => { + return { + eventName: this.eventName, + iteration: this.iteration, + iterationCount: this.iterationCount, + requestName: this.requestName, + requestId: this.requestId, }; + }; } diff --git a/packages/insomnia-scripting-environment/src/objects/request.ts b/packages/insomnia-scripting-environment/src/objects/request.ts index 637b262e66..79af8160f8 100644 --- a/packages/insomnia-scripting-environment/src/objects/request.ts +++ b/packages/insomnia-scripting-environment/src/objects/request.ts @@ -1,5 +1,10 @@ import { type ClientCertificate, init as initClientCertificate } from 'insomnia/src/models/client-certificate'; -import type { Request as InsomniaRequest, RequestBody as InsomniaRequestBody, RequestBodyParameter, RequestPathParameter } from 'insomnia/src/models/request'; +import type { + Request as InsomniaRequest, + RequestBody as InsomniaRequestBody, + RequestBodyParameter, + RequestPathParameter, +} from 'insomnia/src/models/request'; import type { Settings } from 'insomnia/src/models/settings'; import { type AuthOptions, type AuthOptionTypes, fromPreRequestAuth, RequestAuth } from './auth'; @@ -16,708 +21,688 @@ import type { Variable, VariableList } from './variables'; export type RequestBodyMode = undefined | 'formdata' | 'urlencoded' | 'raw' | 'file' | 'graphql'; export interface RequestBodyOptions { - mode: RequestBodyMode; - file?: string; - formdata?: { key: string; value: string; type?: string; disabled?: boolean }[]; - graphql?: { query: string; operationName: string; variables: object; disabled?: boolean }; - raw?: string; - urlencoded?: { key: string; value: string; type?: string; disabled?: boolean; multiline?: boolean | string; fileName?: string }[]; - options?: object; -} - -export class FormParam extends Property { + mode: RequestBodyMode; + file?: string; + formdata?: { key: string; value: string; type?: string; disabled?: boolean }[]; + graphql?: { query: string; operationName: string; variables: object; disabled?: boolean }; + raw?: string; + urlencoded?: { key: string; value: string; type?: string; + disabled?: boolean; + multiline?: boolean | string; + fileName?: string; + }[]; + options?: object; +} - constructor(options: { key: string; value: string; type?: string; disabled?: boolean }) { - super(); - this.key = options.key; - this.value = options.value; - this.type = options.type; - this.disabled = options.disabled; - } +export class FormParam extends Property { + key: string; + value: string; + type?: string; - static _postman_propertyAllowsMultipleValues() { - throw Error('unsupported'); - } + constructor(options: { key: string; value: string; type?: string; disabled?: boolean }) { + super(); + this.key = options.key; + this.value = options.value; + this.type = options.type; + this.disabled = options.disabled; + } - static _postman_propertyIndexKey() { - throw Error('unsupported'); - } + static _postman_propertyAllowsMultipleValues() { + throw Error('unsupported'); + } - // TODO: it is not supported yet in existing scripts - // static parse(param: FormParam) { - // } + static _postman_propertyIndexKey() { + throw Error('unsupported'); + } - override toJSON() { - return { key: this.key, value: this.value, type: this.type, disabled: this.disabled }; - } + // TODO: it is not supported yet in existing scripts + // static parse(param: FormParam) { + // } - override toString() { - const key = encodeURIComponent(this.key); - const value = encodeURIComponent(this.value); - return `${key}=${value}`; - } + override toJSON() { + return { key: this.key, value: this.value, type: this.type, disabled: this.disabled }; + } - override valueOf() { - return this.value; - } + override toString() { + const key = encodeURIComponent(this.key); + const value = encodeURIComponent(this.value); + return `${key}=${value}`; + } + + override valueOf() { + return this.value; + } } function getClassFields(opts: RequestBodyOptions) { - const formdata = opts.formdata ? - new PropertyList( - FormParam, - undefined, - opts.formdata. - map(formParamObj => new FormParam({ - ...formParamObj, - })) - ) : - undefined; + const formdata = opts.formdata + ? new PropertyList( + FormParam, + undefined, + opts.formdata.map( + formParamObj => + new FormParam({ + ...formParamObj, + }), + ), + ) + : undefined; - let urlencoded = undefined; - if (opts.urlencoded != null) { - if (typeof opts.urlencoded === 'string') { - const queryParamObj = QueryParam.parse(opts.urlencoded); - urlencoded = new PropertyList( - QueryParam, - undefined, - Object.entries(queryParamObj) - .map(entry => ({ key: entry[0], value: JSON.stringify(entry[1]) })) - .map(kv => new QueryParam(kv)), - ); - } else { - urlencoded = new PropertyList( - QueryParam, - undefined, - opts.urlencoded - .map(entry => ({ - key: entry.key, - value: entry.value, - type: entry.type, - disabled: entry.disabled, - fileName: entry.fileName, - multiline: entry.multiline, - })) - .map(kv => new QueryParam(kv)), - ); - } + let urlencoded = undefined; + if (opts.urlencoded != null) { + if (typeof opts.urlencoded === 'string') { + const queryParamObj = QueryParam.parse(opts.urlencoded); + urlencoded = new PropertyList( + QueryParam, + undefined, + Object.entries(queryParamObj) + .map(entry => ({ key: entry[0], value: JSON.stringify(entry[1]) })) + .map(kv => new QueryParam(kv)), + ); + } else { + urlencoded = new PropertyList( + QueryParam, + undefined, + opts.urlencoded + .map(entry => ({ + key: entry.key, + value: entry.value, + type: entry.type, + disabled: entry.disabled, + fileName: entry.fileName, + multiline: entry.multiline, + })) + .map(kv => new QueryParam(kv)), + ); } + } - return { - mode: opts.mode, - file: opts.file, - graphql: opts.graphql, - raw: opts.raw, - options: opts.options, - formdata, - urlencoded, - }; + return { + mode: opts.mode, + file: opts.file, + graphql: opts.graphql, + raw: opts.raw, + options: opts.options, + formdata, + urlencoded, + }; } export class RequestBody extends PropertyBase { - mode: RequestBodyMode; // type of request data - // It can be a file path (when used with Node.js) or a unique ID (when used with the browser). - // or it could be "data:application/octet-stream;base64" - file?: string; - formdata?: PropertyList; - graphql?: { query: string; operationName: string; variables: object }; // raw graphql data - // TODO: option's usage is unknown - raw?: string; // raw body - urlencoded?: PropertyList; // URL encoded body params - options?: object; // request body options + mode: RequestBodyMode; // type of request data + // It can be a file path (when used with Node.js) or a unique ID (when used with the browser). + // or it could be "data:application/octet-stream;base64" + file?: string; + formdata?: PropertyList; + graphql?: { query: string; operationName: string; variables: object }; // raw graphql data + // TODO: option's usage is unknown + raw?: string; // raw body + urlencoded?: PropertyList; // URL encoded body params + options?: object; // request body options - constructor(opts: RequestBodyOptions) { - super(); + constructor(opts: RequestBodyOptions) { + super(); - const transformedOpts = getClassFields(opts); - this.mode = transformedOpts.mode; - this.file = transformedOpts.file; - this.formdata = transformedOpts.formdata; - this.graphql = transformedOpts.graphql; - this.options = transformedOpts.options; - this.raw = transformedOpts.raw; - this.urlencoded = transformedOpts.urlencoded; + const transformedOpts = getClassFields(opts); + this.mode = transformedOpts.mode; + this.file = transformedOpts.file; + this.formdata = transformedOpts.formdata; + this.graphql = transformedOpts.graphql; + this.options = transformedOpts.options; + this.raw = transformedOpts.raw; + this.urlencoded = transformedOpts.urlencoded; + } + + isEmpty() { + switch (this.mode) { + case 'formdata': + return this.formdata == null; + case 'urlencoded': + return this.urlencoded == null; + case 'raw': + return this.raw == null; + case 'file': + return this.file == null; + case 'graphql': + return this.graphql == null; + default: + throw Error(`isEmpty: mode (${this.mode}) is unexpected`); } + } - isEmpty() { - switch (this.mode) { - case 'formdata': - return this.formdata == null; - case 'urlencoded': - return this.urlencoded == null; - case 'raw': - return this.raw == null; - case 'file': - return this.file == null; - case 'graphql': - return this.graphql == null; - default: - throw Error(`isEmpty: mode (${this.mode}) is unexpected`); - } - } - - override toString() { - if (this.mode === undefined) { - return ''; - } - - try { - switch (this.mode) { - case 'formdata': - return this.formdata?.map(param => param.toString(), {}).join('&') || ''; - case 'urlencoded': - return this.urlencoded?.map(param => param.toString(), {}).join('&') || ''; - case 'raw': - return this.raw || ''; - case 'file': - return this.file || ''; - case 'graphql': - return this.graphql ? JSON.stringify(this.graphql) : ''; - default: - throw Error(`mode (${this.mode}) is unexpected`); - } - } catch (e) { - throw Error(`toString: ${e}`); - } - } - - update(opts: RequestBodyOptions) { - const transformedOpts = getClassFields(opts); - this.mode = transformedOpts.mode; - this.file = transformedOpts.file; - this.formdata = transformedOpts.formdata; - this.graphql = transformedOpts.graphql; - this.options = transformedOpts.options; - this.raw = transformedOpts.raw; - this.urlencoded = transformedOpts.urlencoded; - } -} - -export interface RequestOptions { - url: string | Url; - method?: string; - header?: HeaderDefinition[] | object; - body?: RequestBodyOptions; - auth?: AuthOptions; - proxy?: ProxyConfigOptions; - certificate?: CertificateOptions; - pathParameters?: RequestPathParameter[]; - name?: string; -} - -export interface RequestSize { - body: number; - header: number; - total: number; - source: string; -} - -function requestOptionsToClassFields(options: RequestOptions) { - const url = toUrlObject(options.url); - const method = options.method || 'GET'; - - let headers: HeaderList
; - if (options.header != null) { - if (Array.isArray(options.header)) { - headers = new HeaderList( - undefined, - options.header ? options.header.map(header => new Header(header)) : [], - ); - } else { - headers = new HeaderList( - undefined, - Object.entries(options.header) - .map(entry => new Header({ key: entry[0], value: entry[1] })), - ); - } - } else { - headers = new HeaderList(undefined, new Array
()); - } - - const body = options.body ? new RequestBody(options.body) : undefined; - const auth = new RequestAuth(options.auth || { type: 'noauth' }); - const proxy = options.proxy ? new ProxyConfig(options.proxy) : undefined; - const certificate = options.certificate ? new Certificate(options.certificate) : undefined; - const pathParameters = options.pathParameters ? options.pathParameters : new Array(); - - return { - name: options.name || '', - url, - method, - headers, - body, - auth, - proxy, - certificate, - pathParameters, - }; -} - -export class Request extends Property { - override name: string; - url: Url; - method: string; - headers: HeaderList
; - body?: RequestBody; - auth: RequestAuth; - proxy?: ProxyConfig; - certificate?: Certificate; - pathParameters: RequestPathParameter[]; - - constructor(options: RequestOptions) { - super(); - - this._kind = 'Request'; - - const transformedOpts = requestOptionsToClassFields(options); - - this.name = transformedOpts.name; - this.url = transformedOpts.url; - this.method = transformedOpts.method; - this.headers = transformedOpts.headers; - this.body = transformedOpts.body; - this.auth = transformedOpts.auth; - this.proxy = transformedOpts.proxy; - this.certificate = transformedOpts.certificate; - this.pathParameters = transformedOpts.pathParameters; - } - - static isRequest(obj: object) { - return '_kind' in obj && obj._kind === 'Request'; - } - - addHeader(header: Header | object) { - if (Header.isHeader(header)) { - const headerInstance = header as Header; - this.headers.add(headerInstance); - } else if ('key' in header && 'value' in header) { - const headerInstance = new Header(header); - this.headers.add(headerInstance); - } else { - throw Error('header must be Header | {key: string; value: string}'); - } - } - - addQueryParams(params: QueryParam[] | string) { - this.url.addQueryParams(params); - } - - authorizeUsing(authType: AuthOptionTypes | AuthOptions, options?: VariableList) { - const selectedAuth = typeof authType === 'string' ? authType : authType.type; - this.auth.use(selectedAuth, options || { type: 'noauth' }); - } - - clone() { - return new Request({ ...this.toJSON() }); - } - - forEachHeader(callback: (header: Header, context?: object) => void) { - this.headers.each(callback, {}); - } - - getHeaders(options?: { - ignoreCase: boolean; - enabled: boolean; - multiValue: boolean; - sanitizeKeys: boolean; - }) { - // merge headers with same key into an array - const headerMap = new Map(); - this.headers.each(header => { - // if the disable is null, it means enabled. - const enabled = options?.enabled ? header.disabled == null || !header.disabled : true; - const isFalsyValue = options?.sanitizeKeys ? !header.value : false; - const hasName = !!header.key; - - if (!enabled || isFalsyValue || !hasName) { - return; - } - - header.key = options?.ignoreCase ? header.key?.toLocaleLowerCase() : header.key; - - if (headerMap.has(header.key)) { - const existingHeader = headerMap.get(header.key) || []; - headerMap.set(header.key, [...existingHeader, header.value]); - } else { - headerMap.set(header.key, [header.value]); - } - }, {}); - - const headersObj: Record = {}; - Array.from(headerMap.entries()) - .forEach(headerEntry => { - headersObj[headerEntry[0]] = headerEntry[1]; - }); - - return headersObj; - } - - removeHeader(toRemove: string | Header, options?: { ignoreCase: boolean }) { - const filteredHeaders = this.headers.filter( - header => { - if (!header.key) { - return false; - } - - if (typeof toRemove === 'string') { - return options != null && options.ignoreCase ? - header.key.toLocaleLowerCase() !== toRemove.toLocaleLowerCase() : - header.key !== toRemove; - } else if (toRemove instanceof Header) { - if (!toRemove.key) { - return false; - } - - return options != null && options.ignoreCase ? - header.key.toLocaleLowerCase() !== toRemove.key.toLocaleLowerCase() : - header.key !== toRemove.key; - } - throw Error('type of the "toRemove" must be: string | Header'); - - }, - {}, - ); - - this.headers = new HeaderList(undefined, filteredHeaders); - } - - removeQueryParams(params: string | string[] | QueryParam[]) { - this.url.removeQueryParams(params); - } - - size(): RequestSize { - return calculatePayloadSize((this.body || '').toString(), this.headers); - } - - override toJSON() { - return { - url: this.url.toString(), - method: this.method, - header: this.headers.map(header => header.toJSON(), {}), - body: { - mode: this.body?.mode, - file: this.body?.file, - formdata: this.body?.formdata?.map(formParam => formParam.toJSON(), {}), - graphql: this.body?.graphql, - raw: this.body?.raw, - urlencoded: this.body?.urlencoded?.map(queryParam => queryParam.toJSON(), {}), - }, - auth: this.auth.toJSON(), - proxy: this.proxy ? { - match: this.proxy.match, - host: this.proxy.host, - port: this.proxy.port, - tunnel: this.proxy.tunnel, - disabled: this.proxy.disabled, - authenticate: this.proxy.authenticate, - username: this.proxy.username, - password: this.proxy.password, - protocol: this.proxy.protocol, - } : undefined, - certificate: this.certificate ? { - name: this.certificate?.name, - matches: this.certificate?.matches?.map(match => match.toString(), {}), - key: this.certificate?.key, - cert: this.certificate?.cert, - passphrase: this.certificate?.passphrase, - pfx: this.certificate?.pfx, - } : undefined, - }; - } - - update(options: RequestOptions) { - const transformedOptions = requestOptionsToClassFields(options); - - this.name = transformedOptions.name; - this.url = transformedOptions.url; - this.method = transformedOptions.method; - this.headers = transformedOptions.headers; - this.body = transformedOptions.body; - this.auth = transformedOptions.auth; - this.proxy = transformedOptions.proxy; - this.certificate = transformedOptions.certificate; - this.pathParameters = transformedOptions.pathParameters; - } - - upsertHeader(header: HeaderDefinition) { - // remove keys with same name - this.headers = new HeaderList( - undefined, - this.headers - .filter( - existingHeader => existingHeader.key !== header.key, - {}, - ) - ); - - // append new - this.headers.append(new Header(header)); - } -} - -export function mergeSettings( - originalSettings: Settings, - updatedReq: Request, -): Settings { - const proxyEnabled = updatedReq.proxy != null - && !updatedReq.proxy.disabled - && updatedReq.proxy.getProxyUrl() !== ''; - if (!proxyEnabled) { - return originalSettings; - } - - const proxyUrl = updatedReq.proxy?.getProxyUrl(); - if (!proxyUrl) { - return originalSettings; - } - - // it always override both http and https proxies - const httpProxy = proxyUrl; - const httpsProxy = proxyUrl; - - return { - ...originalSettings, - proxyEnabled, - httpProxy, - httpsProxy, - }; -} - -export function mergeClientCertificates( - originalClientCertificates: ClientCertificate[], - updatedReq: Request, -): ClientCertificate[] { - // as Pre-request script request only supports one certificate while Insomnia supports configuring multiple ones - // then the mapping rule is: - // - if the pre-request script request cert is specified, it replaces all original certs - // - if not, it returns original certs - - if (!updatedReq.certificate) { - return originalClientCertificates; - } else if ( - updatedReq.certificate.key == null && - updatedReq.certificate.cert == null && - updatedReq.certificate.pfx == null - ) { - return originalClientCertificates; - } - - const baseCertificate = originalClientCertificates && originalClientCertificates.length > 0 ? - { - // TODO: remove baseModelPart currently it is necessary for type checking - ...initClientCertificate(), - ...originalClientCertificates[0], - } : - { - // TODO: remove baseModelPart currently it is necessary for type checking - ...initClientCertificate(), - _id: '', - type: '', - parentId: '', - modified: 0, - created: 0, - isPrivate: false, - name: '', - }; - - if (updatedReq.certificate.pfx && updatedReq.certificate.pfx?.src !== '') { - const specifiedCert: ClientCertificate = { - ...baseCertificate, - key: null, - cert: null, - name: updatedReq.certificate.name || '', - disabled: updatedReq.certificate.disabled || false, - passphrase: updatedReq.certificate.passphrase || null, - pfx: updatedReq.certificate.pfx?.src, - host: '*', - }; - - return [specifiedCert, ...originalClientCertificates]; - } else if ( - updatedReq && - updatedReq.certificate.key && - updatedReq.certificate.cert && - updatedReq.certificate.key?.src !== '' && - updatedReq.certificate.cert?.src !== '' - ) { - const specifiedCert: ClientCertificate = { - ...baseCertificate, - - _id: '', - type: '', - parentId: '', - modified: 0, - created: 0, - isPrivate: false, - name: updatedReq.certificate.name || '', - disabled: updatedReq.certificate.disabled || false, - host: '*', - key: updatedReq.certificate.key?.src, - cert: updatedReq.certificate.cert?.src, - passphrase: updatedReq.certificate.passphrase || null, - pfx: null, - }; - - return [specifiedCert, ...originalClientCertificates]; - } - - throw Error('Invalid certificate configuration: "cert+key" and "pfx" can not be set at the same time'); -} - -export function toScriptRequestBody(insomniaReqBody: InsomniaRequestBody): RequestBodyOptions { - let reqBodyOpt: RequestBodyOptions = { mode: undefined }; - - if (insomniaReqBody.text !== undefined) { - reqBodyOpt = { - mode: 'raw', - raw: insomniaReqBody.text, - }; - } else if (insomniaReqBody.fileName !== undefined && insomniaReqBody.fileName !== '') { - reqBodyOpt = { - mode: 'file', - file: insomniaReqBody.fileName, - }; - } else if (insomniaReqBody.params !== undefined) { - reqBodyOpt = { - mode: 'urlencoded', - urlencoded: insomniaReqBody.params.map( - (param: RequestBodyParameter) => ({ - key: param.name, - value: param.value, - type: param.type, - multiline: param.multiline, - disabled: param.disabled, - fileName: param.fileName, - }) - ), - }; - } - - return reqBodyOpt; -} - -export function mergeRequestBody( - updatedReqBody: RequestBody | undefined, - originalReqBody: InsomniaRequestBody -): InsomniaRequestBody { - let mimeType = 'application/octet-stream'; - if (updatedReqBody) { - switch (updatedReqBody.mode) { - case undefined: - mimeType = 'application/octet-stream'; - break; - case 'raw': - mimeType = 'text/plain'; - break; - case 'file': - // TODO: improve this by sniffing - mimeType = 'application/octet-stream'; - break; - case 'formdata': - // boundary should already be part of Content-Type header - mimeType = 'multipart/form-data'; - break; - case 'urlencoded': - mimeType = 'application/x-www-form-urlencoded'; - break; - case 'graphql': - mimeType = 'application/json'; - break; - default: - throw Error(`unknown request body mode: ${updatedReqBody.mode}`); - } - } - if (originalReqBody.mimeType) { - mimeType = originalReqBody.mimeType; + override toString() { + if (this.mode === undefined) { + return ''; } try { - const textContent = updatedReqBody?.raw !== undefined ? updatedReqBody?.raw : - updatedReqBody?.graphql ? JSON.stringify(updatedReqBody?.graphql) : undefined; - - return { - mimeType: mimeType, - text: textContent, - fileName: updatedReqBody?.file, - params: updatedReqBody?.urlencoded?.map( - (param: QueryParam) => { - return { - name: param.key, - value: param.value, - type: param.type, - fileName: param.fileName, - multiline: param.multiline, - disabled: param.disabled, - }; - }, - {}, - ), - }; + switch (this.mode) { + case 'formdata': + return this.formdata?.map(param => param.toString(), {}).join('&') || ''; + case 'urlencoded': + return this.urlencoded?.map(param => param.toString(), {}).join('&') || ''; + case 'raw': + return this.raw || ''; + case 'file': + return this.file || ''; + case 'graphql': + return this.graphql ? JSON.stringify(this.graphql) : ''; + default: + throw Error(`mode (${this.mode}) is unexpected`); + } } catch (e) { - throw Error(`failed to update body: ${e}`); + throw Error(`toString: ${e}`); } + } + + update(opts: RequestBodyOptions) { + const transformedOpts = getClassFields(opts); + this.mode = transformedOpts.mode; + this.file = transformedOpts.file; + this.formdata = transformedOpts.formdata; + this.graphql = transformedOpts.graphql; + this.options = transformedOpts.options; + this.raw = transformedOpts.raw; + this.urlencoded = transformedOpts.urlencoded; + } } -export function mergeRequests( - originalReq: InsomniaRequest, - updatedReq: Request -): InsomniaRequest { - const queryParamObjects = updatedReq.url.query.map( - queryParam => ({ - name: queryParam.key, - value: queryParam.value, - disabled: queryParam.disabled, - }), - {}, +export interface RequestOptions { + url: string | Url; + method?: string; + header?: HeaderDefinition[] | object; + body?: RequestBodyOptions; + auth?: AuthOptions; + proxy?: ProxyConfigOptions; + certificate?: CertificateOptions; + pathParameters?: RequestPathParameter[]; + name?: string; +} + +export interface RequestSize { + body: number; + header: number; + total: number; + source: string; +} + +function requestOptionsToClassFields(options: RequestOptions) { + const url = toUrlObject(options.url); + const method = options.method || 'GET'; + + let headers: HeaderList
; + if (options.header != null) { + if (Array.isArray(options.header)) { + headers = new HeaderList(undefined, options.header ? options.header.map(header => new Header(header)) : []); + } else { + headers = new HeaderList( + undefined, + Object.entries(options.header).map(entry => new Header({ key: entry[0], value: entry[1] })), + ); + } + } else { + headers = new HeaderList(undefined, new Array
()); + } + + const body = options.body ? new RequestBody(options.body) : undefined; + const auth = new RequestAuth(options.auth || { type: 'noauth' }); + const proxy = options.proxy ? new ProxyConfig(options.proxy) : undefined; + const certificate = options.certificate ? new Certificate(options.certificate) : undefined; + const pathParameters = options.pathParameters ? options.pathParameters : new Array(); + + return { + name: options.name || '', + url, + method, + headers, + body, + auth, + proxy, + certificate, + pathParameters, + }; +} + +export class Request extends Property { + override name: string; + url: Url; + method: string; + headers: HeaderList
; + body?: RequestBody; + auth: RequestAuth; + proxy?: ProxyConfig; + certificate?: Certificate; + pathParameters: RequestPathParameter[]; + + constructor(options: RequestOptions) { + super(); + + this._kind = 'Request'; + + const transformedOpts = requestOptionsToClassFields(options); + + this.name = transformedOpts.name; + this.url = transformedOpts.url; + this.method = transformedOpts.method; + this.headers = transformedOpts.headers; + this.body = transformedOpts.body; + this.auth = transformedOpts.auth; + this.proxy = transformedOpts.proxy; + this.certificate = transformedOpts.certificate; + this.pathParameters = transformedOpts.pathParameters; + } + + static isRequest(obj: object) { + return '_kind' in obj && obj._kind === 'Request'; + } + + addHeader(header: Header | object) { + if (Header.isHeader(header)) { + const headerInstance = header as Header; + this.headers.add(headerInstance); + } else if ('key' in header && 'value' in header) { + const headerInstance = new Header(header); + this.headers.add(headerInstance); + } else { + throw Error('header must be Header | {key: string; value: string}'); + } + } + + addQueryParams(params: QueryParam[] | string) { + this.url.addQueryParams(params); + } + + authorizeUsing(authType: AuthOptionTypes | AuthOptions, options?: VariableList) { + const selectedAuth = typeof authType === 'string' ? authType : authType.type; + this.auth.use(selectedAuth, options || { type: 'noauth' }); + } + + clone() { + return new Request({ ...this.toJSON() }); + } + + forEachHeader(callback: (header: Header, context?: object) => void) { + this.headers.each(callback, {}); + } + + getHeaders(options?: { ignoreCase: boolean; enabled: boolean; multiValue: boolean; sanitizeKeys: boolean }) { + // merge headers with same key into an array + const headerMap = new Map(); + this.headers.each(header => { + // if the disable is null, it means enabled. + const enabled = options?.enabled ? header.disabled == null || !header.disabled : true; + const isFalsyValue = options?.sanitizeKeys ? !header.value : false; + const hasName = !!header.key; + + if (!enabled || isFalsyValue || !hasName) { + return; + } + + header.key = options?.ignoreCase ? header.key?.toLocaleLowerCase() : header.key; + + if (headerMap.has(header.key)) { + const existingHeader = headerMap.get(header.key) || []; + headerMap.set(header.key, [...existingHeader, header.value]); + } else { + headerMap.set(header.key, [header.value]); + } + }, {}); + + const headersObj: Record = {}; + Array.from(headerMap.entries()).forEach(headerEntry => { + headersObj[headerEntry[0]] = headerEntry[1]; + }); + + return headersObj; + } + + removeHeader(toRemove: string | Header, options?: { ignoreCase: boolean }) { + const filteredHeaders = this.headers.filter(header => { + if (!header.key) { + return false; + } + + if (typeof toRemove === 'string') { + return options != null && options.ignoreCase + ? header.key.toLocaleLowerCase() !== toRemove.toLocaleLowerCase() + : header.key !== toRemove; + } else if (toRemove instanceof Header) { + if (!toRemove.key) { + return false; + } + + return options != null && options.ignoreCase + ? header.key.toLocaleLowerCase() !== toRemove.key.toLocaleLowerCase() + : header.key !== toRemove.key; + } + throw Error('type of the "toRemove" must be: string | Header'); + }, {}); + + this.headers = new HeaderList(undefined, filteredHeaders); + } + + removeQueryParams(params: string | string[] | QueryParam[]) { + this.url.removeQueryParams(params); + } + + size(): RequestSize { + return calculatePayloadSize((this.body || '').toString(), this.headers); + } + + override toJSON() { + return { + url: this.url.toString(), + method: this.method, + header: this.headers.map(header => header.toJSON(), {}), + body: { + mode: this.body?.mode, + file: this.body?.file, + formdata: this.body?.formdata?.map(formParam => formParam.toJSON(), {}), + graphql: this.body?.graphql, + raw: this.body?.raw, + urlencoded: this.body?.urlencoded?.map(queryParam => queryParam.toJSON(), {}), + }, + auth: this.auth.toJSON(), + proxy: this.proxy + ? { + match: this.proxy.match, + host: this.proxy.host, + port: this.proxy.port, + tunnel: this.proxy.tunnel, + disabled: this.proxy.disabled, + authenticate: this.proxy.authenticate, + username: this.proxy.username, + password: this.proxy.password, + protocol: this.proxy.protocol, + } + : undefined, + certificate: this.certificate + ? { + name: this.certificate?.name, + matches: this.certificate?.matches?.map(match => match.toString(), {}), + key: this.certificate?.key, + cert: this.certificate?.cert, + passphrase: this.certificate?.passphrase, + pfx: this.certificate?.pfx, + } + : undefined, + }; + } + + update(options: RequestOptions) { + const transformedOptions = requestOptionsToClassFields(options); + + this.name = transformedOptions.name; + this.url = transformedOptions.url; + this.method = transformedOptions.method; + this.headers = transformedOptions.headers; + this.body = transformedOptions.body; + this.auth = transformedOptions.auth; + this.proxy = transformedOptions.proxy; + this.certificate = transformedOptions.certificate; + this.pathParameters = transformedOptions.pathParameters; + } + + upsertHeader(header: HeaderDefinition) { + // remove keys with same name + this.headers = new HeaderList( + undefined, + this.headers.filter(existingHeader => existingHeader.key !== header.key, {}), ); - const updatedReqProperties: Partial = { - name: updatedReq.name, - url: updatedReq.url.toStringWithoutQuery(), - method: updatedReq.method, - body: mergeRequestBody(updatedReq.body, originalReq.body), - headers: updatedReq.headers - .map( - (header: Header) => ({ - name: header.key, - value: header.value, - disabled: header.disabled, - }), - {}, - ), - authentication: fromPreRequestAuth(updatedReq.auth), - preRequestScript: '', - pathParameters: updatedReq.pathParameters, - parameters: queryParamObjects, + + // append new + this.headers.append(new Header(header)); + } +} + +export function mergeSettings(originalSettings: Settings, updatedReq: Request): Settings { + const proxyEnabled = updatedReq.proxy != null && !updatedReq.proxy.disabled && updatedReq.proxy.getProxyUrl() !== ''; + if (!proxyEnabled) { + return originalSettings; + } + + const proxyUrl = updatedReq.proxy?.getProxyUrl(); + if (!proxyUrl) { + return originalSettings; + } + + // it always override both http and https proxies + const httpProxy = proxyUrl; + const httpsProxy = proxyUrl; + + return { + ...originalSettings, + proxyEnabled, + httpProxy, + httpsProxy, + }; +} + +export function mergeClientCertificates( + originalClientCertificates: ClientCertificate[], + updatedReq: Request, +): ClientCertificate[] { + // as Pre-request script request only supports one certificate while Insomnia supports configuring multiple ones + // then the mapping rule is: + // - if the pre-request script request cert is specified, it replaces all original certs + // - if not, it returns original certs + + if (!updatedReq.certificate) { + return originalClientCertificates; + } else if ( + updatedReq.certificate.key == null && + updatedReq.certificate.cert == null && + updatedReq.certificate.pfx == null + ) { + return originalClientCertificates; + } + + const baseCertificate = + originalClientCertificates && originalClientCertificates.length > 0 + ? { + // TODO: remove baseModelPart currently it is necessary for type checking + ...initClientCertificate(), + ...originalClientCertificates[0], + } + : { + // TODO: remove baseModelPart currently it is necessary for type checking + ...initClientCertificate(), + _id: '', + type: '', + parentId: '', + modified: 0, + created: 0, + isPrivate: false, + name: '', + }; + + if (updatedReq.certificate.pfx && updatedReq.certificate.pfx?.src !== '') { + const specifiedCert: ClientCertificate = { + ...baseCertificate, + key: null, + cert: null, + name: updatedReq.certificate.name || '', + disabled: updatedReq.certificate.disabled || false, + passphrase: updatedReq.certificate.passphrase || null, + pfx: updatedReq.certificate.pfx?.src, + host: '*', }; - return { - ...originalReq, - ...updatedReqProperties, + return [specifiedCert, ...originalClientCertificates]; + } else if ( + updatedReq && + updatedReq.certificate.key && + updatedReq.certificate.cert && + updatedReq.certificate.key?.src !== '' && + updatedReq.certificate.cert?.src !== '' + ) { + const specifiedCert: ClientCertificate = { + ...baseCertificate, + + _id: '', + type: '', + parentId: '', + modified: 0, + created: 0, + isPrivate: false, + name: updatedReq.certificate.name || '', + disabled: updatedReq.certificate.disabled || false, + host: '*', + key: updatedReq.certificate.key?.src, + cert: updatedReq.certificate.cert?.src, + passphrase: updatedReq.certificate.passphrase || null, + pfx: null, }; + + return [specifiedCert, ...originalClientCertificates]; + } + + throw Error('Invalid certificate configuration: "cert+key" and "pfx" can not be set at the same time'); +} + +export function toScriptRequestBody(insomniaReqBody: InsomniaRequestBody): RequestBodyOptions { + let reqBodyOpt: RequestBodyOptions = { mode: undefined }; + + if (insomniaReqBody.text !== undefined) { + reqBodyOpt = { + mode: 'raw', + raw: insomniaReqBody.text, + }; + } else if (insomniaReqBody.fileName !== undefined && insomniaReqBody.fileName !== '') { + reqBodyOpt = { + mode: 'file', + file: insomniaReqBody.fileName, + }; + } else if (insomniaReqBody.params !== undefined) { + reqBodyOpt = { + mode: 'urlencoded', + urlencoded: insomniaReqBody.params.map((param: RequestBodyParameter) => ({ + key: param.name, + value: param.value, + type: param.type, + multiline: param.multiline, + disabled: param.disabled, + fileName: param.fileName, + })), + }; + } + + return reqBodyOpt; +} + +export function mergeRequestBody( + updatedReqBody: RequestBody | undefined, + originalReqBody: InsomniaRequestBody, +): InsomniaRequestBody { + let mimeType = 'application/octet-stream'; + if (updatedReqBody) { + switch (updatedReqBody.mode) { + case undefined: + mimeType = 'application/octet-stream'; + break; + case 'raw': + mimeType = 'text/plain'; + break; + case 'file': + // TODO: improve this by sniffing + mimeType = 'application/octet-stream'; + break; + case 'formdata': + // boundary should already be part of Content-Type header + mimeType = 'multipart/form-data'; + break; + case 'urlencoded': + mimeType = 'application/x-www-form-urlencoded'; + break; + case 'graphql': + mimeType = 'application/json'; + break; + default: + throw Error(`unknown request body mode: ${updatedReqBody.mode}`); + } + } + if (originalReqBody.mimeType) { + mimeType = originalReqBody.mimeType; + } + + try { + const textContent = + updatedReqBody?.raw !== undefined + ? updatedReqBody?.raw + : updatedReqBody?.graphql + ? JSON.stringify(updatedReqBody?.graphql) + : undefined; + + return { + mimeType: mimeType, + text: textContent, + fileName: updatedReqBody?.file, + params: updatedReqBody?.urlencoded?.map((param: QueryParam) => { + return { + name: param.key, + value: param.value, + type: param.type, + fileName: param.fileName, + multiline: param.multiline, + disabled: param.disabled, + }; + }, {}), + }; + } catch (e) { + throw Error(`failed to update body: ${e}`); + } +} + +export function mergeRequests(originalReq: InsomniaRequest, updatedReq: Request): InsomniaRequest { + const queryParamObjects = updatedReq.url.query.map( + queryParam => ({ + name: queryParam.key, + value: queryParam.value, + disabled: queryParam.disabled, + }), + {}, + ); + const updatedReqProperties: Partial = { + name: updatedReq.name, + url: updatedReq.url.toStringWithoutQuery(), + method: updatedReq.method, + body: mergeRequestBody(updatedReq.body, originalReq.body), + headers: updatedReq.headers.map( + (header: Header) => ({ + name: header.key, + value: header.value, + disabled: header.disabled, + }), + {}, + ), + authentication: fromPreRequestAuth(updatedReq.auth), + preRequestScript: '', + pathParameters: updatedReq.pathParameters, + parameters: queryParamObjects, + }; + + return { + ...originalReq, + ...updatedReqProperties, + }; } export function calculatePayloadSize(body: string, headers: HeaderList
): RequestSize { - const bodySize = new Blob([body]).size; - const headerSize = calculateHeadersSize(headers); - return { - body: bodySize, - header: headerSize, - total: bodySize + headerSize, - source: 'COMPUTED', - }; + const bodySize = new Blob([body]).size; + const headerSize = calculateHeadersSize(headers); + return { + body: bodySize, + header: headerSize, + total: bodySize + headerSize, + source: 'COMPUTED', + }; } export function calculateHeadersSize(headers: HeaderList
): number { - const headerSize = new Blob([ - headers.reduce( - (acc, header) => (acc + header.toString() + '\n'), - '', - {}, - ), - ]).size; + const headerSize = new Blob([headers.reduce((acc, header) => acc + header.toString() + '\n', '', {})]).size; - return headerSize; + return headerSize; } diff --git a/packages/insomnia-scripting-environment/src/objects/response.ts b/packages/insomnia-scripting-environment/src/objects/response.ts index fd5213f27a..85026708f9 100644 --- a/packages/insomnia-scripting-environment/src/objects/response.ts +++ b/packages/insomnia-scripting-environment/src/objects/response.ts @@ -12,326 +12,317 @@ import type { Request } from './request'; import { calculateHeadersSize } from './request'; export interface ResponseOptions { - code: number; - reason?: string; - header?: HeaderDefinition[]; - cookie?: CookieOptions[]; - body?: string; - // ideally it should work in both browser and node - stream?: Buffer | ArrayBuffer; - responseTime: number; - originalRequest: Request; - bytesRead?: number; // this is from Insomnia for returning response size() directly + code: number; + reason?: string; + header?: HeaderDefinition[]; + cookie?: CookieOptions[]; + body?: string; + // ideally it should work in both browser and node + stream?: Buffer | ArrayBuffer; + responseTime: number; + originalRequest: Request; + bytesRead?: number; // this is from Insomnia for returning response size() directly } export interface ResponseContentInfo { - mimeType: string; - mimeFormat: string; - charset: string; - fileExtension: string; - fileName: string; - contentType: string; + mimeType: string; + mimeFormat: string; + charset: string; + fileExtension: string; + fileName: string; + contentType: string; } // TODO: unknown usage // export interface Timings export class Response extends Property { - body: string; - code: number; - cookies: CookieList; - headers: HeaderList
; - originalRequest: Request; - responseTime: number; - status: string; - stream?: Buffer | ArrayBuffer; + body: string; + code: number; + cookies: CookieList; + headers: HeaderList
; + originalRequest: Request; + responseTime: number; + status: string; + stream?: Buffer | ArrayBuffer; - private bytesRead: number; // + private bytesRead: number; // - constructor(options: ResponseOptions) { - super(); + constructor(options: ResponseOptions) { + super(); - this._kind = 'Response'; + this._kind = 'Response'; - this.body = options.body || ''; - this.code = options.code; - this.cookies = new CookieList( - options.cookie?.map(cookie => new Cookie(cookie)) || [], - ); - this.headers = new HeaderList( - undefined, - options.header?.map(headerOpt => new Header(headerOpt)) || [], - ); - this.originalRequest = options.originalRequest; - this.responseTime = options.responseTime; - this.stream = options.stream; - const detectedStatus = options.reason || RESPONSE_CODE_REASONS[options.code]; - if (!detectedStatus) { - throw Error(`Response constructor: reason or code field must be set in the options(reason: ${options.reason}, code:${options.code})`); - } else { - this.status = detectedStatus; - } - - this.bytesRead = options.bytesRead || 0; + this.body = options.body || ''; + this.code = options.code; + this.cookies = new CookieList(options.cookie?.map(cookie => new Cookie(cookie)) || []); + this.headers = new HeaderList(undefined, options.header?.map(headerOpt => new Header(headerOpt)) || []); + this.originalRequest = options.originalRequest; + this.responseTime = options.responseTime; + this.stream = options.stream; + const detectedStatus = options.reason || RESPONSE_CODE_REASONS[options.code]; + if (!detectedStatus) { + throw Error( + `Response constructor: reason or code field must be set in the options(reason: ${options.reason}, code:${options.code})`, + ); + } else { + this.status = detectedStatus; } - // TODO: the accurate type of the response should be given - static createFromNode( - response: { - body: string; - headers: HeaderDefinition[]; - statusCode: number; - statusMessage: string; - elapsedTime: number; - originalRequest: Request; - stream: Buffer | ArrayBuffer; - }, - cookies: CookieOptions[], - ) { - return new Response({ - cookie: cookies, - body: response.body.toString(), - stream: response.stream, - header: response.headers, - code: response.statusCode, - reason: response.statusMessage, - responseTime: response.elapsedTime, - originalRequest: response.originalRequest, + this.bytesRead = options.bytesRead || 0; + } + + // TODO: the accurate type of the response should be given + static createFromNode( + response: { + body: string; + headers: HeaderDefinition[]; + statusCode: number; + statusMessage: string; + elapsedTime: number; + originalRequest: Request; + stream: Buffer | ArrayBuffer; + }, + cookies: CookieOptions[], + ) { + return new Response({ + cookie: cookies, + body: response.body.toString(), + stream: response.stream, + header: response.headers, + code: response.statusCode, + reason: response.statusMessage, + responseTime: response.elapsedTime, + originalRequest: response.originalRequest, + }); + } + + static isResponse(obj: object) { + return '_kind' in obj && obj._kind === 'Response'; + } + + contentInfo(): ResponseContentInfo { + const mimeInfo = { + mimeType: 'application/octet-stream', + mimeFormat: '', // TODO: it's definition is unknown + charset: 'utf-8', + }; + + const contentType = this.headers.find(header => header.key === 'Content-Type'); + if (contentType) { + const directives = contentType.valueOf().split('; '); + if (directives.length === 0) { + throw Error('contentInfo: header Content-Type value is blank'); + } else { + const mimeType = directives[0]; + if (!mimeType) { + throw Error('contentInfo: mime type in header Content-Type is invalid'); + } + mimeInfo.mimeType = mimeType; + directives.forEach(dir => { + if (dir.startsWith('charset')) { + mimeInfo.charset = dir.slice(dir.indexOf('=') + 1); + } }); + } } - static isResponse(obj: object) { - return '_kind' in obj && obj._kind === 'Response'; - } + const fileInfo = { + extension: '', + name: 'unknown', + }; - contentInfo(): ResponseContentInfo { - const mimeInfo = { - mimeType: 'application/octet-stream', - mimeFormat: '', // TODO: it's definition is unknown - charset: 'utf-8', - }; - - const contentType = this.headers.find(header => header.key === 'Content-Type'); - if (contentType) { - const directives = contentType.valueOf().split('; '); - if (directives.length === 0) { - throw Error('contentInfo: header Content-Type value is blank'); - } else { - const mimeType = directives[0]; - if (!mimeType) { - throw Error('contentInfo: mime type in header Content-Type is invalid'); - } - mimeInfo.mimeType = mimeType; - directives.forEach(dir => { - if (dir.startsWith('charset')) { - mimeInfo.charset = dir.slice(dir.indexOf('=') + 1); - } - }); - } + const contentDisposition = this.headers.find(header => header.key === 'Content-Disposition'); + if (contentDisposition) { + const directives = contentDisposition.valueOf().split('; '); + directives.forEach(dir => { + if (dir.startsWith('filename')) { + const fileName = (fileInfo.extension = dir.slice(dir.indexOf('=') + 1)); + fileInfo.name = fileName.slice(1, fileName.lastIndexOf('.')); // ignore '"' arounds the file name + fileInfo.extension = fileName.slice(fileName.lastIndexOf('.') + 1, fileName.length - 1); } + }); + } - const fileInfo = { - extension: '', - name: 'unknown', - }; + return { + mimeType: mimeInfo.mimeType, + mimeFormat: mimeInfo.mimeFormat, + charset: mimeInfo.charset, + fileExtension: fileInfo.extension, + fileName: fileInfo.name, + contentType: contentType?.valueOf() || 'application/octet-stream', + }; + } - const contentDisposition = this.headers.find(header => header.key === 'Content-Disposition');; - if (contentDisposition) { - const directives = contentDisposition.valueOf().split('; '); - directives.forEach(dir => { - if (dir.startsWith('filename')) { - const fileName = fileInfo.extension = dir.slice(dir.indexOf('=') + 1); - fileInfo.name = fileName.slice(1, fileName.lastIndexOf('.')); // ignore '"' arounds the file name - fileInfo.extension = fileName.slice(fileName.lastIndexOf('.') + 1, fileName.length - 1); - } - }); + dataURI() { + const contentInfo = this.contentInfo(); + const bodyInBase64 = this.stream || this.body; + if (!bodyInBase64) { + throw Error('dataURI(): response body is not defined'); + } + + return `data:${contentInfo.contentType};baseg4, ${bodyInBase64}`; + } + + json(reviver?: (key: string, value: any) => any, _strict?: boolean) { + // TODO: enable strict after common module is introduced + try { + return JSON.parse(this.body.toString(), reviver); + } catch (e) { + throw Error(`json: failed to parse: ${e}`); + } + } + + jsonp(_reviver?: (key: string, value: any) => any, _strict?: boolean) { + throw unsupportedError('jsonp()'); + } + + reason() { + return this.status; + } + + size() { + const headerSize = calculateHeadersSize(this.headers); + return { + body: this.bytesRead, + header: headerSize, + total: this.bytesRead + headerSize, + source: 'COMPUTED', + }; + } + + text() { + return this.body.toString(); + } + + // Besides chai.expect, "to" is extended to support cases like: + // insomnia.response.to.have.status(200); + // insomnia.response.to.not.have.status(200); + get to() { + type valueType = boolean | number | string | object | undefined; + + const verify = (got: valueType, expected: valueType, checkEquality = true) => { + if (['boolean', 'number', 'string', 'undefined'].includes(typeof got)) { + if ((checkEquality && expected === got) || (!checkEquality && expected !== got)) { + return; } + } else if ( + (checkEquality && deepEqual(got, expected, { strict: true })) || + (!checkEquality && !deepEqual(got, expected, { strict: true })) + ) { + return; + } + throw Error(`"${got}" is not equal to the expected value: "${expected}"`); + }; + const haveStatus = (expected: number | string, checkEquality: boolean) => { + if (typeof expected === 'string') { + verify(this.status, expected, checkEquality); + } else { + verify(this.code, expected, checkEquality); + } + }; + const haveHeader = (expected: string, checkEquality: boolean) => + verify(this.headers.toObject().find(header => header.key === expected) !== undefined, checkEquality); + const haveBody = (expected: string, checkEquality: boolean) => verify(this.text(), expected, checkEquality); + const haveJsonBody = (expected: object, checkEquality: boolean) => verify(this.json(), expected, checkEquality); + const haveJsonSchema = (expected: object, checkEquality: boolean) => { + const ajv = new Ajv(); - return { - mimeType: mimeInfo.mimeType, - mimeFormat: mimeInfo.mimeFormat, - charset: mimeInfo.charset, - fileExtension: fileInfo.extension, - fileName: fileInfo.name, - contentType: contentType?.valueOf() || 'application/octet-stream', - }; - } - - dataURI() { - const contentInfo = this.contentInfo(); - const bodyInBase64 = this.stream || this.body; - if (!bodyInBase64) { - throw Error('dataURI(): response body is not defined'); + try { + const jsonBody = JSON.parse(this.body); + const schemaMatched = ajv.validate(expected, jsonBody); + if ((schemaMatched && checkEquality) || (!schemaMatched && !checkEquality)) { + return; } + } catch (e) { + throw Error(`Failed to verify response body schema, response could not be a valid json: "${e}"`); + } + throw Error("Response's schema is not equal to the expected value"); + }; - return `data:${contentInfo.contentType};baseg4, ${bodyInBase64}`; - } - - - json(reviver?: (key: string, value: any) => any, _strict?: boolean) { - // TODO: enable strict after common module is introduced - try { - return JSON.parse(this.body.toString(), reviver); - } catch (e) { - throw Error(`json: failed to parse: ${e}`); - } - } - - - jsonp(_reviver?: (key: string, value: any) => any, _strict?: boolean) { - throw unsupportedError('jsonp()'); - } - - reason() { - return this.status; - } - - size() { - const headerSize = calculateHeadersSize(this.headers); - return { - body: this.bytesRead, - header: headerSize, - total: this.bytesRead + headerSize, - source: 'COMPUTED', - }; - } - - text() { - return this.body.toString(); - } - - // Besides chai.expect, "to" is extended to support cases like: - // insomnia.response.to.have.status(200); - // insomnia.response.to.not.have.status(200); - get to() { - type valueType = boolean | number | string | object | undefined; - - const verify = (got: valueType, expected: valueType, checkEquality = true) => { - if (['boolean', 'number', 'string', 'undefined'].includes(typeof got)) { - if ((checkEquality && expected === got) || (!checkEquality && expected !== got)) { - return; - } - } else if ( - (checkEquality && deepEqual(got, expected, { strict: true })) || - (!checkEquality && !deepEqual(got, expected, { strict: true })) - ) { - return; - } - throw Error(`"${got}" is not equal to the expected value: "${expected}"`); - }; - const haveStatus = (expected: number | string, checkEquality: boolean) => { - if (typeof expected === 'string') { - verify(this.status, expected, checkEquality); - } else { - verify(this.code, expected, checkEquality); - } - }; - const haveHeader = (expected: string, checkEquality: boolean) => verify( - this.headers.toObject().find(header => header.key === expected) !== undefined, - checkEquality, - ); - const haveBody = (expected: string, checkEquality: boolean) => verify(this.text(), expected, checkEquality); - const haveJsonBody = (expected: object, checkEquality: boolean) => verify(this.json(), expected, checkEquality); - const haveJsonSchema = (expected: object, checkEquality: boolean) => { - const ajv = new Ajv(); - - try { - const jsonBody = JSON.parse(this.body); - const schemaMatched = ajv.validate(expected, jsonBody); - if ((schemaMatched && checkEquality) || (!schemaMatched && !checkEquality)) { - return; - } - } catch (e) { - throw Error(`Failed to verify response body schema, response could not be a valid json: "${e}"`); - } - throw Error("Response's schema is not equal to the expected value"); - }; - - return { - // follows extend chai's chains for compatibility - have: { - status: (expected: number | string) => haveStatus(expected, true), - header: (expected: string) => haveHeader(expected, true), - body: (expected: string) => haveBody(expected, true), - jsonBody: (expected: object) => haveJsonBody(expected, true), - jsonSchema: (expected: object) => haveJsonSchema(expected, true), - }, - not: { - have: { - status: (expected: number | string) => haveStatus(expected, false), - header: (expected: string) => haveHeader(expected, false), - body: (expected: string) => haveBody(expected, false), - jsonBody: (expected: object) => haveJsonBody(expected, false), - jsonSchema: (expected: object) => haveJsonSchema(expected, false), - }, - }, - }; - } + return { + // follows extend chai's chains for compatibility + have: { + status: (expected: number | string) => haveStatus(expected, true), + header: (expected: string) => haveHeader(expected, true), + body: (expected: string) => haveBody(expected, true), + jsonBody: (expected: object) => haveJsonBody(expected, true), + jsonSchema: (expected: object) => haveJsonSchema(expected, true), + }, + not: { + have: { + status: (expected: number | string) => haveStatus(expected, false), + header: (expected: string) => haveHeader(expected, false), + body: (expected: string) => haveBody(expected, false), + jsonBody: (expected: object) => haveJsonBody(expected, false), + jsonSchema: (expected: object) => haveJsonSchema(expected, false), + }, + }, + }; + } } export function toScriptResponse( - originalRequest: Request, - partialInsoResponse: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError, - responseBody: string, + originalRequest: Request, + partialInsoResponse: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError, + responseBody: string, ): Response | undefined { - if ('error' in partialInsoResponse) { - // it is sendCurlAndWriteTimelineError and basically doesn't contain anything useful - return undefined; - } - const partialResponse = partialInsoResponse as sendCurlAndWriteTimelineResponse; + if ('error' in partialInsoResponse) { + // it is sendCurlAndWriteTimelineError and basically doesn't contain anything useful + return undefined; + } + const partialResponse = partialInsoResponse as sendCurlAndWriteTimelineResponse; - const headers = partialResponse.headers ? - partialResponse.headers.map( - insoHeader => ({ - key: insoHeader.name, - value: insoHeader.value, - }), - {}, - ) - : []; + const headers = partialResponse.headers + ? partialResponse.headers.map( + insoHeader => ({ + key: insoHeader.name, + value: insoHeader.value, + }), + {}, + ) + : []; - const insoCookieOptions = partialResponse.headers ? - partialResponse.headers - .filter( - header => { - return header.name.toLowerCase() === 'set-cookie'; - }, - {}, - ).map( - setCookieHeader => Cookie.parse(setCookieHeader.value) - ) - : []; + const insoCookieOptions = partialResponse.headers + ? partialResponse.headers + .filter(header => { + return header.name.toLowerCase() === 'set-cookie'; + }, {}) + .map(setCookieHeader => Cookie.parse(setCookieHeader.value)) + : []; - const responseOption = { - code: partialResponse.statusCode || 0, - reason: partialResponse.statusMessage, - header: headers, - cookie: insoCookieOptions, - body: responseBody, - // stream is duplicated with body - responseTime: partialResponse.elapsedTime, - originalRequest, - bytesRead: partialResponse.bytesRead, - }; + const responseOption = { + code: partialResponse.statusCode || 0, + reason: partialResponse.statusMessage, + header: headers, + cookie: insoCookieOptions, + body: responseBody, + // stream is duplicated with body + responseTime: partialResponse.elapsedTime, + originalRequest, + bytesRead: partialResponse.bytesRead, + }; - return new Response(responseOption); -}; - -export async function readBodyFromPath(response: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError | undefined) { - // it allows to execute scripts (e.g., for testing) but body contains nothing - if (!response || 'error' in response) { - return ''; - } else if (!response.bodyPath) { - return ''; - } - const nodejsReadCurlResponse = process.type === 'renderer' ? window.bridge.readCurlResponse : readCurlResponse; - const readResponseResult = await nodejsReadCurlResponse({ - bodyPath: response.bodyPath, - bodyCompression: response.bodyCompression, - }); - - if (readResponseResult.error) { - throw Error(`Failed to read body: ${readResponseResult.error}`); - } - return readResponseResult.body; + return new Response(responseOption); +} + +export async function readBodyFromPath( + response: sendCurlAndWriteTimelineResponse | sendCurlAndWriteTimelineError | undefined, +) { + // it allows to execute scripts (e.g., for testing) but body contains nothing + if (!response || 'error' in response) { + return ''; + } else if (!response.bodyPath) { + return ''; + } + const nodejsReadCurlResponse = process.type === 'renderer' ? window.bridge.readCurlResponse : readCurlResponse; + const readResponseResult = await nodejsReadCurlResponse({ + bodyPath: response.bodyPath, + bodyCompression: response.bodyCompression, + }); + + if (readResponseResult.error) { + throw Error(`Failed to read body: ${readResponseResult.error}`); + } + return readResponseResult.body; } diff --git a/packages/insomnia-scripting-environment/src/objects/send-request.ts b/packages/insomnia-scripting-environment/src/objects/send-request.ts index 8d0addcfd9..24917f8e89 100644 --- a/packages/insomnia-scripting-environment/src/objects/send-request.ts +++ b/packages/insomnia-scripting-environment/src/objects/send-request.ts @@ -11,272 +11,276 @@ import { Request, type RequestOptions } from './request'; import { Response } from './response'; export async function sendRequest( - request: string | Request | RequestOptions, - cb: (error?: string, response?: Response) => void, - settings: Settings, + request: string | Request | RequestOptions, + cb: (error?: string, response?: Response) => void, + settings: Settings, ): Promise { - return new Promise(async resolve => { - // TODO(george): enable cascading cancellation later as current solution just adds complexity - const requestOptions = requestToCurlOptions(request, settings); + return new Promise(async resolve => { + // TODO(george): enable cascading cancellation later as current solution just adds complexity + const requestOptions = requestToCurlOptions(request, settings); - try { - const nodejsCurlRequest = process.type === 'renderer' ? window.bridge.curlRequest : (await import('insomnia/src/main/network/libcurl-promise')).curlRequest; - nodejsCurlRequest(requestOptions) - .then((result: any) => { - const output = result as CurlRequestOutput; - return curlOutputToResponse(output, request); - }).then((transformedOutput: Response) => { - cb(undefined, transformedOutput); - resolve(transformedOutput); - }).catch(e => { - cb(e, undefined); - resolve(undefined); - }); - } catch (err: any) { - if (err.name === 'AbortError') { - cb(`Request was cancelled: ${err.message}`, undefined); - } else { - cb(`Something went wrong: ${err.message}`, undefined); - } - resolve(undefined); - } - }); -}; + try { + const nodejsCurlRequest = + process.type === 'renderer' + ? window.bridge.curlRequest + : (await import('insomnia/src/main/network/libcurl-promise')).curlRequest; + nodejsCurlRequest(requestOptions) + .then((result: any) => { + const output = result as CurlRequestOutput; + return curlOutputToResponse(output, request); + }) + .then((transformedOutput: Response) => { + cb(undefined, transformedOutput); + resolve(transformedOutput); + }) + .catch(e => { + cb(e, undefined); + resolve(undefined); + }); + } catch (err: any) { + if (err.name === 'AbortError') { + cb(`Request was cancelled: ${err.message}`, undefined); + } else { + cb(`Something went wrong: ${err.message}`, undefined); + } + resolve(undefined); + } + }); +} function requestToCurlOptions(req: string | Request | RequestOptions, settings: Settings) { - const id = uuidv4(); - const settingFollowRedirects: 'global' | 'on' | 'off' = settings.followRedirects ? 'on' : 'off'; + const id = uuidv4(); + const settingFollowRedirects: 'global' | 'on' | 'off' = settings.followRedirects ? 'on' : 'off'; - if (typeof req === 'string') { - return { - requestId: `pre-request-script-adhoc-req-simple:${id}`, - req: { - headers: [], - method: 'GET', - body: { mimeType: undefined }, // no body is set so it's type is undefined - authentication: fromPreRequestAuth( - new RequestAuth({ type: 'noauth' }), - ), - settingFollowRedirects: settingFollowRedirects, - settingRebuildPath: true, - settingSendCookies: true, - url: req, - // currently cookies should be handled by user in headers - cookieJar: { - cookies: [], - }, - cookies: [], - suppressUserAgent: false, - }, - finalUrl: req, - settings, - certificates: [], - caCertficatePath: null, - socketPath: undefined, - authHeader: undefined, // TODO: add this for bearer and other auth methods - }; - } else if (req instanceof Request || typeof req === 'object') { - const finalReq = req instanceof Request ? req : new Request(req); + if (typeof req === 'string') { + return { + requestId: `pre-request-script-adhoc-req-simple:${id}`, + req: { + headers: [], + method: 'GET', + body: { mimeType: undefined }, // no body is set so it's type is undefined + authentication: fromPreRequestAuth(new RequestAuth({ type: 'noauth' })), + settingFollowRedirects: settingFollowRedirects, + settingRebuildPath: true, + settingSendCookies: true, + url: req, + // currently cookies should be handled by user in headers + cookieJar: { + cookies: [], + }, + cookies: [], + suppressUserAgent: false, + }, + finalUrl: req, + settings, + certificates: [], + caCertficatePath: null, + socketPath: undefined, + authHeader: undefined, // TODO: add this for bearer and other auth methods + }; + } else if (req instanceof Request || typeof req === 'object') { + const finalReq = req instanceof Request ? req : new Request(req); - let mimeType = 'application/octet-stream'; - if (finalReq.body) { - switch (finalReq.body.mode) { - case 'raw': - mimeType = 'text/plain'; - break; - case 'file': - // TODO: improve this by sniffing - mimeType = 'application/octet-stream'; - break; - case 'formdata': - // boundary should already be part of Content-Type header - mimeType = 'multipart/form-data'; - break; - case 'urlencoded': - mimeType = 'application/x-www-form-urlencoded'; - break; - case 'graphql': - mimeType = 'application/json'; - break; - default: - throw Error(`unknown body mode: ${finalReq.body.mode}`); - } - } - - // const authHeaders = []; - // const authObj = fromPreRequestAuth(finalReq.auth); - // switch (authObj.type) { - // case 'apikey': - // if (authObj.in === 'header') { - // authHeaders.push({ - // name: authObj.key, - // value: authObj.key, - // }); - // } - // case 'bearer': - // authHeaders.push({ - // name: 'Authorization', - // value: `Bearer ${authObj.token}`, - // }); - // default: - // // TODO: support other methods - // } - - const urlencodedParams = finalReq.body?.urlencoded?.all().map( - param => ({ name: param.key, value: param.value }), - ); - const formdataParams = finalReq.body?.formdata?.all().map( - param => ({ - type: param.type, - name: param.key, - value: param.type === 'file' ? '' : param.value, - fileName: param.type === 'file' ? param.value : '', - }), - ); - - const params = finalReq.body?.mode === 'formdata' || finalReq.body?.mode === 'urlencoded' ? - finalReq.body?.mode === 'formdata' ? formdataParams : urlencodedParams : - []; - - return { - requestId: finalReq.id || `pre-request-script-adhoc-req-custom:${id}`, - req: { - headers: finalReq.headers.map(header => ({ name: header.key, value: header.value }), {}), - method: finalReq.method, - body: { - mimeType, - method: finalReq.method, - text: finalReq.body?.toString(), - params, - fileName: finalReq.body?.mode === 'file' ? finalReq.body?.toString() : undefined, - }, - authentication: fromPreRequestAuth(finalReq.auth), - settingFollowRedirects: settingFollowRedirects, - settingRebuildPath: true, - settingSendCookies: true, - url: finalReq.url.toString(), - // currently cookies should be handled by user in headers - cookieJar: { - cookies: [], - }, - cookies: [], - suppressUserAgent: finalReq.headers.map( - h => h.key.toLowerCase() === 'user-agent' && h.disabled === true, - {}, - ).length > 0, - }, - finalUrl: finalReq.url.toString(), - settings, - certificates: finalReq.certificate ? - [{ - host: finalReq.certificate?.name || '', - passphrase: finalReq.certificate?.passphrase || '', - cert: finalReq.certificate?.cert?.src || '', - key: finalReq.certificate?.key?.src || '', - pfx: finalReq.certificate?.pfx?.src || '', - // unused fields because they are not persisted - disabled: false, - isPrivate: false, - _id: '', - type: '', - parentId: '', - modified: 0, - created: 0, - name: '', - }] : - [], - caCertficatePath: null, // the request in pre-request script doesn't support customizing ca yet - socketPath: undefined, - authHeader: undefined, // TODO: add this for bearer and other auth methods - }; + let mimeType = 'application/octet-stream'; + if (finalReq.body) { + switch (finalReq.body.mode) { + case 'raw': + mimeType = 'text/plain'; + break; + case 'file': + // TODO: improve this by sniffing + mimeType = 'application/octet-stream'; + break; + case 'formdata': + // boundary should already be part of Content-Type header + mimeType = 'multipart/form-data'; + break; + case 'urlencoded': + mimeType = 'application/x-www-form-urlencoded'; + break; + case 'graphql': + mimeType = 'application/json'; + break; + default: + throw Error(`unknown body mode: ${finalReq.body.mode}`); + } } - throw Error('the request type must be: string | Request | RequestOptions.'); + // const authHeaders = []; + // const authObj = fromPreRequestAuth(finalReq.auth); + // switch (authObj.type) { + // case 'apikey': + // if (authObj.in === 'header') { + // authHeaders.push({ + // name: authObj.key, + // value: authObj.key, + // }); + // } + // case 'bearer': + // authHeaders.push({ + // name: 'Authorization', + // value: `Bearer ${authObj.token}`, + // }); + // default: + // // TODO: support other methods + // } + + const urlencodedParams = finalReq.body?.urlencoded?.all().map(param => ({ name: param.key, value: param.value })); + const formdataParams = finalReq.body?.formdata?.all().map(param => ({ + type: param.type, + name: param.key, + value: param.type === 'file' ? '' : param.value, + fileName: param.type === 'file' ? param.value : '', + })); + + const params = + finalReq.body?.mode === 'formdata' || finalReq.body?.mode === 'urlencoded' + ? finalReq.body?.mode === 'formdata' + ? formdataParams + : urlencodedParams + : []; + + return { + requestId: finalReq.id || `pre-request-script-adhoc-req-custom:${id}`, + req: { + headers: finalReq.headers.map(header => ({ name: header.key, value: header.value }), {}), + method: finalReq.method, + body: { + mimeType, + method: finalReq.method, + text: finalReq.body?.toString(), + params, + fileName: finalReq.body?.mode === 'file' ? finalReq.body?.toString() : undefined, + }, + authentication: fromPreRequestAuth(finalReq.auth), + settingFollowRedirects: settingFollowRedirects, + settingRebuildPath: true, + settingSendCookies: true, + url: finalReq.url.toString(), + // currently cookies should be handled by user in headers + cookieJar: { + cookies: [], + }, + cookies: [], + suppressUserAgent: + finalReq.headers.map(h => h.key.toLowerCase() === 'user-agent' && h.disabled === true, {}).length > 0, + }, + finalUrl: finalReq.url.toString(), + settings, + certificates: finalReq.certificate + ? [ + { + host: finalReq.certificate?.name || '', + passphrase: finalReq.certificate?.passphrase || '', + cert: finalReq.certificate?.cert?.src || '', + key: finalReq.certificate?.key?.src || '', + pfx: finalReq.certificate?.pfx?.src || '', + // unused fields because they are not persisted + disabled: false, + isPrivate: false, + _id: '', + type: '', + parentId: '', + modified: 0, + created: 0, + name: '', + }, + ] + : [], + caCertficatePath: null, // the request in pre-request script doesn't support customizing ca yet + socketPath: undefined, + authHeader: undefined, // TODO: add this for bearer and other auth methods + }; + } + + throw Error('the request type must be: string | Request | RequestOptions.'); } async function curlOutputToResponse( - result: CurlRequestOutput, - request: string | Request | RequestOptions, + result: CurlRequestOutput, + request: string | Request | RequestOptions, ): Promise { - if (result.headerResults.length === 0) { - throw Error('curlOutputToResponse: no header result is found'); - } - if (result.patch.error) { - throw result.patch.error; - } + if (result.headerResults.length === 0) { + throw Error('curlOutputToResponse: no header result is found'); + } + if (result.patch.error) { + throw result.patch.error; + } - const lastRedirect = result.headerResults[result.headerResults.length - 1]; - if (!lastRedirect) { - throw Error('curlOutputToResponse: the lastRedirect is not defined'); - } + const lastRedirect = result.headerResults[result.headerResults.length - 1]; + if (!lastRedirect) { + throw Error('curlOutputToResponse: the lastRedirect is not defined'); + } - const originalRequest = typeof request === 'string' ? - new Request({ url: request, method: 'GET' }) : - request instanceof Request ? - request : - new Request(request); + const originalRequest = + typeof request === 'string' + ? new Request({ url: request, method: 'GET' }) + : request instanceof Request + ? request + : new Request(request); - const headers = lastRedirect.headers.map( - (header: { name: string; value: string }) => ({ key: header.name, value: header.value }) - ); + const headers = lastRedirect.headers.map((header: { name: string; value: string }) => ({ + key: header.name, + value: header.value, + })); - const cookieHeaders = lastRedirect.headers.filter(header => { - return header.name.toLowerCase() === 'set-cookie'; - }); - // TODO: tackle stream field but currently it is just a duplication of body - const cookies = cookieHeaders - .map(cookieHeader => { - const cookieObj = Cookie.parse(cookieHeader.value || '', { loose: true }); - if (cookieObj) { - return { - key: cookieObj.key, - value: cookieObj.value, - expires: cookieObj.expires, - maxAge: cookieObj.maxAge, - domain: cookieObj.domain, - path: cookieObj.path, - secure: cookieObj.secure, - httpOnly: cookieObj.httpOnly, - hostOnly: cookieObj.hostOnly, - // session: cookieObj.session, // not supported - // extensions: cookieObj.extensions, - }; - } + const cookieHeaders = lastRedirect.headers.filter(header => { + return header.name.toLowerCase() === 'set-cookie'; + }); + // TODO: tackle stream field but currently it is just a duplication of body + const cookies = cookieHeaders + .map(cookieHeader => { + const cookieObj = Cookie.parse(cookieHeader.value || '', { loose: true }); + if (cookieObj) { + return { + key: cookieObj.key, + value: cookieObj.value, + expires: cookieObj.expires, + maxAge: cookieObj.maxAge, + domain: cookieObj.domain, + path: cookieObj.path, + secure: cookieObj.secure, + httpOnly: cookieObj.httpOnly, + hostOnly: cookieObj.hostOnly, + // session: cookieObj.session, // not supported + // extensions: cookieObj.extensions, + }; + } - return cookieObj; - }) - .filter(cookieOpt => cookieOpt !== undefined); + return cookieObj; + }) + .filter(cookieOpt => cookieOpt !== undefined); - if (!result.responseBodyPath) { - return new Response({ - code: lastRedirect.code, - reason: lastRedirect.reason, - header: headers, - cookie: cookies as CookieOptions[], - body: '', - stream: undefined, - responseTime: result.patch.elapsedTime, - originalRequest, - }); - } - const nodejsReadCurlResponse = process.type === 'renderer' ? window.bridge.readCurlResponse : readCurlResponse; - const bodyResult = await nodejsReadCurlResponse({ - bodyPath: result.responseBodyPath, - bodyCompression: result.patch.bodyCompression, - }); - if (bodyResult.error) { - throw Error(bodyResult.error); - } + if (!result.responseBodyPath) { return new Response({ - code: lastRedirect.code, - reason: lastRedirect.reason, - header: headers, - cookie: cookies as CookieOptions[], - body: bodyResult.body, - // stream is always undefined - // because it is inaccurate to differentiate if body is binary - stream: undefined, - responseTime: result.patch.elapsedTime, - originalRequest, + code: lastRedirect.code, + reason: lastRedirect.reason, + header: headers, + cookie: cookies as CookieOptions[], + body: '', + stream: undefined, + responseTime: result.patch.elapsedTime, + originalRequest, }); + } + const nodejsReadCurlResponse = process.type === 'renderer' ? window.bridge.readCurlResponse : readCurlResponse; + const bodyResult = await nodejsReadCurlResponse({ + bodyPath: result.responseBodyPath, + bodyCompression: result.patch.bodyCompression, + }); + if (bodyResult.error) { + throw Error(bodyResult.error); + } + return new Response({ + code: lastRedirect.code, + reason: lastRedirect.reason, + header: headers, + cookie: cookies as CookieOptions[], + body: bodyResult.body, + // stream is always undefined + // because it is inaccurate to differentiate if body is binary + stream: undefined, + responseTime: result.patch.elapsedTime, + originalRequest, + }); } diff --git a/packages/insomnia-scripting-environment/src/objects/test.ts b/packages/insomnia-scripting-environment/src/objects/test.ts index 8b32ce3b00..2276e55c5b 100644 --- a/packages/insomnia-scripting-environment/src/objects/test.ts +++ b/packages/insomnia-scripting-environment/src/objects/test.ts @@ -1,71 +1,63 @@ -export async function test( - msg: string, - fn: () => Promise, - log: (testResult: RequestTestResult) => void, -) { - const wrapFn = async () => { - const started = performance.now(); +export async function test(msg: string, fn: () => Promise, log: (testResult: RequestTestResult) => void) { + const wrapFn = async () => { + const started = performance.now(); - try { - await fn(); + try { + await fn(); - const executionTime = performance.now() - started; - log({ - testCase: msg, - status: 'passed', - executionTime, - category: 'unknown', - }); - } catch (e) { - const executionTime = performance.now() - started; - log({ - testCase: msg, - status: 'failed', - executionTime, - errorMessage: `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}`, - category: 'unknown', - }); - } - }; + const executionTime = performance.now() - started; + log({ + testCase: msg, + status: 'passed', + executionTime, + category: 'unknown', + }); + } catch (e) { + const executionTime = performance.now() - started; + log({ + testCase: msg, + status: 'failed', + executionTime, + errorMessage: `error: ${e} | ACTUAL: ${e.actual} | EXPECTED: ${e.expected}`, + category: 'unknown', + }); + } + }; - const testPromise = wrapFn(); - startTestObserver(testPromise); - return testPromise; + const testPromise = wrapFn(); + startTestObserver(testPromise); + return testPromise; } let testPromises = new Array>(); export async function waitForAllTestsDone() { - await Promise.allSettled(testPromises); - testPromises = []; + await Promise.allSettled(testPromises); + testPromises = []; } function startTestObserver(promise: Promise) { - testPromises.push(promise); + testPromises.push(promise); } -export async function skip( - msg: string, - _: () => Promise, - log: (testResult: RequestTestResult) => void, -) { - log({ - testCase: msg, - status: 'skipped', - executionTime: 0, - category: 'unknown', - }); +export async function skip(msg: string, _: () => Promise, log: (testResult: RequestTestResult) => void) { + log({ + testCase: msg, + status: 'skipped', + executionTime: 0, + category: 'unknown', + }); } export type TestStatus = 'passed' | 'failed' | 'skipped'; export type TestCategory = 'unknown' | 'pre-request' | 'after-response'; export interface RequestTestResult { - testCase: string; - status: TestStatus; - executionTime: number; // milliseconds - errorMessage?: string; - category: TestCategory; + testCase: string; + status: TestStatus; + executionTime: number; // milliseconds + errorMessage?: string; + category: TestCategory; } export interface TestHandler { - (msg: string, fn: () => Promise): Promise; - skip?: (msg: string, fn: () => Promise) => void; -}; + (msg: string, fn: () => Promise): Promise; + skip?: (msg: string, fn: () => Promise) => void; +} diff --git a/packages/insomnia-scripting-environment/src/objects/urls.ts b/packages/insomnia-scripting-environment/src/objects/urls.ts index 40429f5464..1ad2e6b296 100644 --- a/packages/insomnia-scripting-environment/src/objects/urls.ts +++ b/packages/insomnia-scripting-environment/src/objects/urls.ts @@ -3,370 +3,364 @@ import { checkIfUrlIncludesTag } from './utils'; let UrlSearchParams = URLSearchParams; export function setUrlSearchParams(provider: any) { - UrlSearchParams = provider; + UrlSearchParams = provider; } export interface QueryParamOptions { - key: string; - value: string; - type?: string; - multiline?: string | boolean; - disabled?: boolean; - fileName?: string; + key: string; + value: string; + type?: string; + multiline?: string | boolean; + disabled?: boolean; + fileName?: string; } export class QueryParam extends Property { - override _kind = 'QueryParam'; + override _kind = 'QueryParam'; - key: string; - value: string; - type?: string; - // the `multiline` and `fileName` are properties from Insomnia - // they are added here to avoid being dropped - multiline?: string | boolean; - fileName?: string; + key: string; + value: string; + type?: string; + // the `multiline` and `fileName` are properties from Insomnia + // they are added here to avoid being dropped + multiline?: string | boolean; + fileName?: string; - constructor(options: QueryParamOptions | string) { - super(); + constructor(options: QueryParamOptions | string) { + super(); - if (typeof options === 'string') { - try { - const optionsObj = JSON.parse(options); - this.key = optionsObj.key; - this.value = optionsObj.value; - this.type = optionsObj.type; - this.multiline = optionsObj.multiline; - this.disabled = optionsObj.disabled; - this.fileName = optionsObj.fileName; - } catch (e) { - throw Error(`invalid QueryParam options ${e}`); - } - } else if (typeof options === 'object' && ('key' in options) && ('value' in options)) { - this.key = options.key; - this.value = options.value; - this.type = options.type; - this.multiline = options.multiline; - this.disabled = options.disabled; - this.fileName = options.fileName; - } else { - throw Error('unknown options for new QueryParam'); - } + if (typeof options === 'string') { + try { + const optionsObj = JSON.parse(options); + this.key = optionsObj.key; + this.value = optionsObj.value; + this.type = optionsObj.type; + this.multiline = optionsObj.multiline; + this.disabled = optionsObj.disabled; + this.fileName = optionsObj.fileName; + } catch (e) { + throw Error(`invalid QueryParam options ${e}`); + } + } else if (typeof options === 'object' && 'key' in options && 'value' in options) { + this.key = options.key; + this.value = options.value; + this.type = options.type; + this.multiline = options.multiline; + this.disabled = options.disabled; + this.fileName = options.fileName; + } else { + throw Error('unknown options for new QueryParam'); + } + } + + // TODO: + // (static) _postman_propertyAllowsMultipleValues :Boolean + // (static) _postman_propertyIndexKey :String + + static override _index = 'key'; + + static parse(queryStr: string) { + const params = new UrlSearchParams(queryStr); + return Array.from(params.entries()).map(entry => ({ key: entry[0], value: entry[1] })); + } + + static parseSingle(paramStr: string, _idx?: number, _all?: string[]) { + const pairs = QueryParam.parse(paramStr); + if (pairs.length === 0) { + throw Error('invalid search query string'); } - // TODO: - // (static) _postman_propertyAllowsMultipleValues :Boolean - // (static) _postman_propertyIndexKey :String + return pairs[0]; + } - static override _index = 'key'; + static unparse(params: QueryParamOptions[] | Record) { + const searchParams = new UrlSearchParams(); - static parse(queryStr: string) { - const params = new UrlSearchParams(queryStr); - return Array.from(params.entries()) - .map(entry => ({ key: entry[0], value: entry[1] })); + if (Array.isArray(params)) { + params.forEach((entry: QueryParamOptions) => searchParams.append(entry.key, entry.value)); + } else { + Object.entries(params).forEach(entry => searchParams.append(entry[0], entry[1])); } + return searchParams.toString(); + } - static parseSingle(paramStr: string, _idx?: number, _all?: string[]) { - const pairs = QueryParam.parse(paramStr); - if (pairs.length === 0) { - throw Error('invalid search query string'); - } + static unparseSingle(obj: { key: string; value: string }) { + if ('key' in obj && 'value' in obj) { + const params = new UrlSearchParams(); + params.append(obj.key, obj.value); - return pairs[0]; + return params.toString(); } + return {}; + } - static unparse(params: QueryParamOptions[] | Record) { - const searchParams = new UrlSearchParams(); + override toString() { + const params = new UrlSearchParams(); + params.append(this.key, this.value); - if (Array.isArray(params)) { - params.forEach((entry: QueryParamOptions) => searchParams.append(entry.key, entry.value)); - } else { - Object.entries(params) - .forEach(entry => searchParams.append(entry[0], entry[1])); - } + return params.toString(); + } - return searchParams.toString(); - } - - static unparseSingle(obj: { key: string; value: string }) { - if ('key' in obj && 'value' in obj) { - const params = new UrlSearchParams(); - params.append(obj.key, obj.value); - - return params.toString(); - } - return {}; - } - - override toString() { - const params = new UrlSearchParams(); - params.append(this.key, this.value); - - return params.toString(); - } - - toRawString() { - return `${this.key}=${this.value}`; - } - - update(param: string | { key: string; value: string; type?: string }) { - if (typeof param === 'string') { - const paramObj = QueryParam.parseSingle(param); - if (!paramObj) { - throw Error('failed to update param: input `param` is invalid'); - } - this.key = typeof paramObj.key === 'string' ? paramObj.key : ''; - this.value = typeof paramObj.value === 'string' ? paramObj.value : ''; - } else if ('key' in param && 'value' in param) { - this.key = param.key; - this.value = param.value; - this.type = param.type; - } else { - throw Error('the param for update must be: string | { key: string; value: string }'); - } + toRawString() { + return `${this.key}=${this.value}`; + } + + update(param: string | { key: string; value: string; type?: string }) { + if (typeof param === 'string') { + const paramObj = QueryParam.parseSingle(param); + if (!paramObj) { + throw Error('failed to update param: input `param` is invalid'); + } + this.key = typeof paramObj.key === 'string' ? paramObj.key : ''; + this.value = typeof paramObj.value === 'string' ? paramObj.value : ''; + } else if ('key' in param && 'value' in param) { + this.key = param.key; + this.value = param.value; + this.type = param.type; + } else { + throw Error('the param for update must be: string | { key: string; value: string }'); } + } } export interface UrlOptions { - id?: string; - auth?: { - username: string; - password: string; - }; - hash?: string; - host: string[]; - path?: string[]; - port?: string; - protocol: string; - query: { key: string; value: string }[]; - variables: { key: string; value: string }[]; + id?: string; + auth?: { + username: string; + password: string; + }; + hash?: string; + host: string[]; + path?: string[]; + port?: string; + protocol: string; + query: { key: string; value: string }[]; + variables: { key: string; value: string }[]; } export class Url extends PropertyBase { - override _kind = 'Url'; + override _kind = 'Url'; - id?: string; - private urlObject?: URL; - private origin?: string; - private queryParams: QueryParam[] = []; // query params are handled separately as URL object encodes content + id?: string; + private urlObject?: URL; + private origin?: string; + private queryParams: QueryParam[] = []; // query params are handled separately as URL object encodes content - get auth(): { username: string; password: string } | undefined { - // TODO: probably it should be related to the RequestAuth class - // but the implementation seems only supporting username + password - return this.urlObject && this.urlObject.username !== '' - ? { username: this.urlObject.username, password: this.urlObject.password } - : undefined; + get auth(): { username: string; password: string } | undefined { + // TODO: probably it should be related to the RequestAuth class + // but the implementation seems only supporting username + password + return this.urlObject && this.urlObject.username !== '' + ? { username: this.urlObject.username, password: this.urlObject.password } + : undefined; + } + get hash(): string { + const fullHash = this.urlObject ? this.urlObject.hash : ''; + return fullHash.startsWith('#') ? fullHash.slice(1) : fullHash; + } + get host(): string[] { + return this.urlObject ? this.urlObject.hostname.split('.') : []; + } + get path(): string[] { + return this.urlObject ? this.urlObject.pathname.split('/').filter(segment => segment.trim() !== '') : []; + } + get port(): string { + return this.urlObject ? this.urlObject.port : ''; + } + get protocol(): string { + return this.urlObject ? this.urlObject.protocol : ''; + } + get query(): PropertyList { + return new PropertyList(QueryParam, undefined, this.queryParams); + } + get variables(): string[] { + // TODO: it's usage is unknown + return []; + } + + constructor(def: UrlOptions | string) { + super(); + this.initFields(def); + } + + private initFields(urlOptions: UrlOptions | string | undefined) { + if (typeof urlOptions === 'string') { + // avoid escaping tags by the parser: {% uuid 'v4' %} -> %7B%%20uuid%20'v4'%20%%7D + const ifUrlIncludesTag = checkIfUrlIncludesTag(urlOptions); + if (URL.canParse(urlOptions) && !ifUrlIncludesTag) { + this.urlObject = new URL(urlOptions); + // maintain query params separately + this.urlObject.searchParams.forEach((value: string, key: string) => { + this.queryParams = [...this.queryParams, new QueryParam({ key, value })]; + }); + this.urlObject.search = ''; + } else { + this.urlObject = undefined; + } + this.origin = urlOptions; + } else if (typeof urlOptions === 'object') { + const protocolStr = (urlOptions.protocol || '').trim() ? urlOptions.protocol.trim() : 'https://'; + const authStr = urlOptions.auth ? `${urlOptions.auth.username}:${urlOptions.auth.password}@` : ''; + const hostStr = urlOptions.host.join('.'); + const portStr = urlOptions.port ? `:${urlOptions.port}` : ''; + const pathStr = + urlOptions.path && urlOptions.path.length > 0 + ? `/${urlOptions.path.filter(segment => segment.trim() !== '').join('/')}` + : ''; + const queryStr = + urlOptions.query && urlOptions.query.length > 0 + ? '?' + urlOptions.query.map(pair => `${pair.key}=${pair.value}`).join('&') + : ''; + const hashStr = urlOptions.hash ? `#${urlOptions.hash}` : ''; + + const urlString = `${protocolStr}${authStr}${hostStr}${portStr}${pathStr}${queryStr}${hashStr}`; + + if (URL.canParse(urlString)) { + this.urlObject = new URL(urlString); + // maintain query params separately + this.urlObject.searchParams.forEach((value: string, key: string) => { + this.queryParams = [...this.queryParams, new QueryParam({ key, value })]; + }); + this.urlObject.search = ''; + } else { + this.urlObject = undefined; + } + this.origin = urlString; + } else { + throw Error(`url is invalid: ${urlOptions} `); // TODO: } - get hash(): string { - const fullHash = this.urlObject ? this.urlObject.hash : ''; - return fullHash.startsWith('#') ? fullHash.slice(1) : fullHash; - } - get host(): string[] { - return this.urlObject ? this.urlObject.hostname.split('.') : []; - } - get path(): string[] { - return this.urlObject ? this.urlObject.pathname.split('/').filter(segment => segment.trim() !== '') : []; - } - get port(): string { - return this.urlObject ? this.urlObject.port : ''; - } - get protocol(): string { - return this.urlObject ? this.urlObject.protocol : ''; - } - get query(): PropertyList { - return new PropertyList( - QueryParam, - undefined, - this.queryParams, - ); - } - get variables(): string[] { - // TODO: it's usage is unknown - return []; + } + + static _index = 'id'; + + static isUrl(obj: object) { + return '_kind' in obj && obj._kind === 'Url'; + } + + static parse(urlStr: string): UrlOptions | undefined { + if (URL.canParse(urlStr)) { + const urlObject = new URL(urlStr); + const auth = + urlObject.username === '' ? undefined : { username: urlObject.username, password: urlObject.password }; + const query = Array.from(urlObject.searchParams.entries()).map(entry => ({ key: entry[0], value: entry[1] })); + + return { + auth, + protocol: urlObject.protocol, + host: urlObject.hostname.split('.'), + port: urlObject.port, + path: urlObject.pathname.split('/'), + query, + hash: urlObject.hash, + variables: [], + }; } - constructor( - def: UrlOptions | string - ) { - super(); - this.initFields(def); + return undefined; + } + + addQueryParams(params: QueryParamOptions[] | string) { + if (typeof params === 'string') { + // URLSearchParams is not used here as it encodes content + const pairs = params.split('&'); + pairs.forEach(pair => { + const parts = pair.split('='); + this.queryParams = [...this.queryParams, new QueryParam({ key: parts[0], value: parts[1] })]; + // this.urlObject.searchParams.append(pair[0], pair[1]); + }); + } else if (Array.isArray(params)) { + params.forEach(pair => { + this.queryParams = [...this.queryParams, new QueryParam({ ...pair })]; + }); + } else { + throw Error(`addQueryParams: invalid params: ${JSON.stringify(params)}`); } + } - private initFields(urlOptions: UrlOptions | string | undefined) { - if (typeof urlOptions === 'string') { - // avoid escaping tags by the parser: {% uuid 'v4' %} -> %7B%%20uuid%20'v4'%20%%7D - const ifUrlIncludesTag = checkIfUrlIncludesTag(urlOptions); - if (URL.canParse(urlOptions) && !ifUrlIncludesTag) { - this.urlObject = new URL(urlOptions); - // maintain query params separately - this.urlObject.searchParams.forEach((value: string, key: string) => { - this.queryParams = [...this.queryParams, new QueryParam({ key, value })]; - }); - this.urlObject.search = ''; - } else { - this.urlObject = undefined; - } - this.origin = urlOptions; - } else if (typeof urlOptions === 'object') { - const protocolStr = (urlOptions.protocol || '').trim() ? urlOptions.protocol.trim() : 'https://'; - const authStr = urlOptions.auth ? `${urlOptions.auth.username}:${urlOptions.auth.password}@` : ''; - const hostStr = urlOptions.host.join('.'); - const portStr = urlOptions.port ? `:${urlOptions.port}` : ''; - const pathStr = urlOptions.path && urlOptions.path.length > 0 ? `/${urlOptions.path.filter(segment => segment.trim() !== '').join('/')}` : ''; - const queryStr = urlOptions.query && urlOptions.query.length > 0 ? '?' + urlOptions.query.map(pair => `${pair.key}=${pair.value}`).join('&') : ''; - const hashStr = urlOptions.hash ? `#${urlOptions.hash}` : ''; - - const urlString = `${protocolStr}${authStr}${hostStr}${portStr}${pathStr}${queryStr}${hashStr}`; - - if (URL.canParse(urlString)) { - this.urlObject = new URL(urlString); - // maintain query params separately - this.urlObject.searchParams.forEach((value: string, key: string) => { - this.queryParams = [...this.queryParams, new QueryParam({ key, value })]; - }); - this.urlObject.search = ''; - } else { - this.urlObject = undefined; - } - this.origin = urlString; - } else { - throw Error(`url is invalid: ${urlOptions} `); // TODO: - } + getHost() { + if (this.urlObject) { + return this.urlObject.hostname; } + return ''; + } - static _index = 'id'; - - static isUrl(obj: object) { - return '_kind' in obj && obj._kind === 'Url'; + getPath(_unresolved?: boolean) { + if (this.urlObject) { + return this.urlObject.pathname; } + return ''; + } - static parse(urlStr: string): UrlOptions | undefined { - if (URL.canParse(urlStr)) { - const urlObject = new URL(urlStr); - const auth = urlObject.username === '' ? undefined : { username: urlObject.username, password: urlObject.password }; - const query = Array.from(urlObject.searchParams.entries()) - .map(entry => ({ key: entry[0], value: entry[1] })); - - return { - auth, - protocol: urlObject.protocol, - host: urlObject.hostname.split('.'), - port: urlObject.port, - path: urlObject.pathname.split('/'), - query, - hash: urlObject.hash, - variables: [], - }; - } - - return undefined; + getPathWithQuery() { + if (this.getPath(true).trim() === '') { + return this.getQueryString(); } + return `${this.getPath(true)}?${this.getQueryString()}`; + } - addQueryParams(params: QueryParamOptions[] | string) { - if (typeof params === 'string') { - // URLSearchParams is not used here as it encodes content - const pairs = params.split('&'); - pairs - .forEach(pair => { - const parts = pair.split('='); - this.queryParams = [...this.queryParams, new QueryParam({ key: parts[0], value: parts[1] })]; - // this.urlObject.searchParams.append(pair[0], pair[1]); - }); - } else if (Array.isArray(params)) { - params.forEach(pair => { - this.queryParams = [...this.queryParams, new QueryParam({ ...pair })]; - }); - } else { - throw Error(`addQueryParams: invalid params: ${JSON.stringify(params)}`); - } + getQueryString() { + return this.queryParams.map(param => param.toRawString()).join('&'); + } + + getRemote(_forcePort?: boolean) { + if (this.urlObject) { + return this.urlObject.host; } + return ''; + } - getHost() { - if (this.urlObject) { - return this.urlObject.hostname; - } - return ''; + removeQueryParams(params: QueryParam[] | string[] | string) { + if (typeof params === 'string') { + this.queryParams = this.queryParams.filter(param => param.key !== params); + } else if (Array.isArray(params)) { + this.queryParams = this.queryParams.filter(param => { + const shouldDelete = params.some(paramToRemove => { + if (typeof paramToRemove === 'string') { + return param.key === paramToRemove; + } + return param.key === paramToRemove.key; + }); + + return !shouldDelete; + }); + } else { + throw Error( + 'removeQueryParams: failed to remove query params: unknown params type, only supports QueryParam[], string[] or string', + ); } + } - - getPath(_unresolved?: boolean) { - if (this.urlObject) { - return this.urlObject.pathname; - } - return ''; + override toString(_forceProtocol?: boolean) { + if (this.urlObject) { + const newUrlObject = new URL(this.urlObject.toString()); + newUrlObject.search = this.getQueryString(); + const urlInString = newUrlObject.toString(); + if (this.urlObject.pathname === '/' && urlInString === this.origin + '/') { + // try to avoid replacing empty path with '/' + return urlInString.slice(0, urlInString.length - 1); + } + return urlInString; } + return this.origin || ''; + } - getPathWithQuery() { - if (this.getPath(true).trim() === '') { - return this.getQueryString(); - } - return `${this.getPath(true)}?${this.getQueryString()}`; + toStringWithoutQuery(_forceProtocol?: boolean) { + if (this.urlObject) { + const newUrlObject = new URL(this.urlObject.toString()); + newUrlObject.search = ''; + const urlInString = newUrlObject.toString(); + if (this.urlObject.pathname === '/' && urlInString === this.origin + '/') { + // try to avoid replacing empty path with '/' + return urlInString.slice(0, urlInString.length - 1); + } + return urlInString; } + return this.origin || ''; + } - getQueryString() { - return this.queryParams.map(param => param.toRawString()).join('&'); - } - - - getRemote(_forcePort?: boolean) { - if (this.urlObject) { - return this.urlObject.host; - } - return ''; - } - - removeQueryParams(params: QueryParam[] | string[] | string) { - if (typeof params === 'string') { - this.queryParams = this.queryParams.filter(param => param.key !== params); - } else if (Array.isArray(params)) { - this.queryParams = this.queryParams.filter(param => { - const shouldDelete = params.some(paramToRemove => { - if (typeof paramToRemove === 'string') { - return param.key === paramToRemove; - } - return param.key === paramToRemove.key; - }); - - return !shouldDelete; - }); - } else { - throw Error('removeQueryParams: failed to remove query params: unknown params type, only supports QueryParam[], string[] or string'); - } - } - - - override toString(_forceProtocol?: boolean) { - if (this.urlObject) { - const newUrlObject = new URL(this.urlObject.toString()); - newUrlObject.search = this.getQueryString(); - const urlInString = newUrlObject.toString(); - if (this.urlObject.pathname === '/' && urlInString === this.origin + '/') { - // try to avoid replacing empty path with '/' - return urlInString.slice(0, urlInString.length - 1); - } - return urlInString; - } - return this.origin || ''; - } - - - toStringWithoutQuery(_forceProtocol?: boolean) { - if (this.urlObject) { - const newUrlObject = new URL(this.urlObject.toString()); - newUrlObject.search = ''; - const urlInString = newUrlObject.toString(); - if (this.urlObject.pathname === '/' && urlInString === this.origin + '/') { - // try to avoid replacing empty path with '/' - return urlInString.slice(0, urlInString.length - 1); - } - return urlInString; - } - return this.origin || ''; - } - - update(url: UrlOptions | string) { - this.initFields(url); - } + update(url: UrlOptions | string) { + this.initFields(url); + } } // interface Matcher { @@ -376,248 +370,250 @@ export class Url extends PropertyBase { // UrlMatchPattern implements chrome extension match patterns: // https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns export class UrlMatchPattern extends Property { - // scheme - // scheme: 'http:' | 'https:' | '*' | 'file:'; + // scheme + // scheme: 'http:' | 'https:' | '*' | 'file:'; - // host - // About wildcard: - // If you use a wildcard in the host pattern - // it must be the first or only character, and it must be followed by a period (.) or forward slash (/). + // host + // About wildcard: + // If you use a wildcard in the host pattern + // it must be the first or only character, and it must be followed by a period (.) or forward slash (/). - // path - // Must contain at least a forward slash - // The slash by itself matches any path. + // path + // Must contain at least a forward slash + // The slash by itself matches any path. - // Special cases: https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns#special - // "" - // "file:///" - // "http://localhost/*" - // It doesn't support match patterns for top Level domains (TLD). + // Special cases: https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns#special + // "" + // "file:///" + // "http://localhost/*" + // It doesn't support match patterns for top Level domains (TLD). - override id = ''; - private pattern: string; + override id = ''; + private pattern: string; - constructor(pattern: string) { - super(); + constructor(pattern: string) { + super(); - this.pattern = pattern; + this.pattern = pattern; + } + + static override _index = 'id'; + static readonly MATCH_ALL_URLS: string = ''; + static pattern: string | undefined = undefined; // TODO: its usage is unknown + static readonly PROTOCOL_DELIMITER: string = '+'; + + // TODO: the url can not start with - + + getProtocols(): string[] { + if (this.pattern === '') { + return ['http', 'https', 'file']; } - static override _index = 'id'; - static readonly MATCH_ALL_URLS: string = ''; - static pattern: string | undefined = undefined; // TODO: its usage is unknown - static readonly PROTOCOL_DELIMITER: string = '+'; - - // TODO: the url can not start with - - - getProtocols(): string[] { - if (this.pattern === '') { - return ['http', 'https', 'file']; - } - - const protocolEndPos = this.pattern.indexOf('://'); - if (protocolEndPos < 0) { - return []; - } - - const protocolPattern = this.pattern.slice(0, protocolEndPos); - const protocols = protocolPattern.split(UrlMatchPattern.PROTOCOL_DELIMITER); - - return protocols.map(protocol => protocol.replace(':', '')); + const protocolEndPos = this.pattern.indexOf('://'); + if (protocolEndPos < 0) { + return []; } - test(urlStr: string) { - const protoEndPos = urlStr.indexOf(':'); - const protoStr = urlStr.slice(0, protoEndPos); - const hostStr = this.getHost(urlStr); - const pathStr = this.getPath(this.pattern); - const portStr = this.getPort(urlStr); + const protocolPattern = this.pattern.slice(0, protocolEndPos); + const protocols = protocolPattern.split(UrlMatchPattern.PROTOCOL_DELIMITER); - return this.testProtocol(protoStr) && - this.testHost(hostStr) && - this.testPath(pathStr) && - this.testPort(portStr, protoStr); + return protocols.map(protocol => protocol.replace(':', '')); + } + + test(urlStr: string) { + const protoEndPos = urlStr.indexOf(':'); + const protoStr = urlStr.slice(0, protoEndPos); + const hostStr = this.getHost(urlStr); + const pathStr = this.getPath(this.pattern); + const portStr = this.getPort(urlStr); + + return ( + this.testProtocol(protoStr) && + this.testHost(hostStr) && + this.testPath(pathStr) && + this.testPort(portStr, protoStr) + ); + } + + private getHost(urlStr: string) { + const protocolEndPos = urlStr.indexOf('://') + 3; + const hostBegPos = protocolEndPos; + + const portBegPos = urlStr.indexOf(':', protocolEndPos); + const pathBegPos = urlStr.indexOf('/', protocolEndPos); + const queryBegPos = urlStr.indexOf('?', protocolEndPos); + const hashBegPos = urlStr.indexOf('?', protocolEndPos); + + let hostEndPos = urlStr.length; + if (portBegPos >= 0) { + hostEndPos = portBegPos; + } else if (pathBegPos >= 0) { + hostEndPos = pathBegPos; + } else if (queryBegPos >= 0) { + hostEndPos = queryBegPos; + } else if (hashBegPos >= 0) { + hostEndPos = hashBegPos; } - private getHost(urlStr: string) { - const protocolEndPos = urlStr.indexOf('://') + 3; - const hostBegPos = protocolEndPos; + return urlStr.slice(hostBegPos, hostEndPos); + } - const portBegPos = urlStr.indexOf(':', protocolEndPos); - const pathBegPos = urlStr.indexOf('/', protocolEndPos); - const queryBegPos = urlStr.indexOf('?', protocolEndPos); - const hashBegPos = urlStr.indexOf('?', protocolEndPos); + testHost(hostStr: string) { + const patternSegments = this.getHost(this.pattern).split('.'); - let hostEndPos = urlStr.length; - if (portBegPos >= 0) { - hostEndPos = portBegPos; - } else if (pathBegPos >= 0) { - hostEndPos = pathBegPos; - } else if (queryBegPos >= 0) { - hostEndPos = queryBegPos; - } else if (hashBegPos >= 0) { - hostEndPos = hashBegPos; - } + const inputHostSegments = hostStr.split('.'); - return urlStr.slice(hostBegPos, hostEndPos); + if (patternSegments.length !== inputHostSegments.length) { + return false; } - testHost(hostStr: string) { - const patternSegments = this.getHost(this.pattern).split('.'); - - const inputHostSegments = hostStr.split('.'); - - if (patternSegments.length !== inputHostSegments.length) { - return false; - } - - for (let i = 0; i < patternSegments.length; i++) { - if (patternSegments[i] === '*') { - continue; - } else if (patternSegments[i] !== inputHostSegments[i]) { - return false; - } - } - return true; - } - - private getPath(urlStr: string) { - const protocolEndPos = urlStr.indexOf('://') + 3; - const hostBegPos = protocolEndPos; - const pathBegPos = urlStr.indexOf('/', hostBegPos); - if (pathBegPos < 0) { - return ''; - } - - const queryBegPos = urlStr.indexOf('?'); - const hashBegPos = urlStr.indexOf('#'); - let pathEndPos = urlStr.length; - if (queryBegPos >= 0) { - pathEndPos = queryBegPos; - } else if (hashBegPos >= 0) { - pathEndPos = hashBegPos; - } - - return urlStr.slice(pathBegPos, pathEndPos); - } - - testPath(pathStr: string) { - const patternSegments = this.getPath(this.pattern).split('/'); - const inputSegments = pathStr.split('/'); - - if (patternSegments.length !== inputSegments.length) { - return false; - } - - for (let i = 0; i < patternSegments.length; i++) { - if (patternSegments[i] === '*') { - continue; - } else if (patternSegments[i] !== inputSegments[i]) { - return false; - } - } - return true; - } - - private getPort(urlStr: string) { - const protocolEndPos = urlStr.indexOf('/') + 2; - const hostBegPos = protocolEndPos; - - let portBegPos = urlStr.indexOf(':', protocolEndPos); - if (portBegPos <= 0) { - return ''; - } - portBegPos += 1; // the port is after ':' - - let portEndPos = urlStr.length; - const pathBegPos = urlStr.indexOf('/', hostBegPos); - const queryBegPos = urlStr.indexOf('?'); - const hashBegPos = urlStr.indexOf('#'); - - if (pathBegPos >= 0) { - portEndPos = pathBegPos; - } else if (queryBegPos >= 0) { - portEndPos = queryBegPos; - } else if (hashBegPos >= 0) { - portEndPos = hashBegPos; - } - - return urlStr.slice(portBegPos, portEndPos); - } - - testPort(port: string, protocol: string) { - if (!this.testProtocol(protocol)) { - return false; - } - - const portPattern = this.getPort(this.pattern); - if (portPattern === '*') { - return true; - } else if (portPattern === '' || port === '') { - const protos = this.getProtocols(); - - if (protocol === 'https') { - return protos.includes('https') && ( - (port === '443' && portPattern === '') || - (port === '' && portPattern === '443') || - (port === '' && portPattern === '') - ); - } else if (protocol === 'http') { - return protos.includes('http') && ( - (port === '80' && portPattern === '') || - (port === '' && portPattern === '80') || - (port === '' && portPattern === '') - ); - } - } - - return portPattern === port; - } - - testProtocol(protocol: string) { - const protoPatterns = this.getProtocols(); - - for (const pattern of protoPatterns) { - if (pattern === '*') { - return true; - } else if (pattern === protocol) { - return true; - } - } + for (let i = 0; i < patternSegments.length; i++) { + if (patternSegments[i] === '*') { + continue; + } else if (patternSegments[i] !== inputHostSegments[i]) { return false; + } + } + return true; + } + + private getPath(urlStr: string) { + const protocolEndPos = urlStr.indexOf('://') + 3; + const hostBegPos = protocolEndPos; + const pathBegPos = urlStr.indexOf('/', hostBegPos); + if (pathBegPos < 0) { + return ''; } - override toString() { - return this.pattern; + const queryBegPos = urlStr.indexOf('?'); + const hashBegPos = urlStr.indexOf('#'); + let pathEndPos = urlStr.length; + if (queryBegPos >= 0) { + pathEndPos = queryBegPos; + } else if (hashBegPos >= 0) { + pathEndPos = hashBegPos; } - update(pattern: string) { - this.pattern = pattern; + return urlStr.slice(pathBegPos, pathEndPos); + } + + testPath(pathStr: string) { + const patternSegments = this.getPath(this.pattern).split('/'); + const inputSegments = pathStr.split('/'); + + if (patternSegments.length !== inputSegments.length) { + return false; } + + for (let i = 0; i < patternSegments.length; i++) { + if (patternSegments[i] === '*') { + continue; + } else if (patternSegments[i] !== inputSegments[i]) { + return false; + } + } + return true; + } + + private getPort(urlStr: string) { + const protocolEndPos = urlStr.indexOf('/') + 2; + const hostBegPos = protocolEndPos; + + let portBegPos = urlStr.indexOf(':', protocolEndPos); + if (portBegPos <= 0) { + return ''; + } + portBegPos += 1; // the port is after ':' + + let portEndPos = urlStr.length; + const pathBegPos = urlStr.indexOf('/', hostBegPos); + const queryBegPos = urlStr.indexOf('?'); + const hashBegPos = urlStr.indexOf('#'); + + if (pathBegPos >= 0) { + portEndPos = pathBegPos; + } else if (queryBegPos >= 0) { + portEndPos = queryBegPos; + } else if (hashBegPos >= 0) { + portEndPos = hashBegPos; + } + + return urlStr.slice(portBegPos, portEndPos); + } + + testPort(port: string, protocol: string) { + if (!this.testProtocol(protocol)) { + return false; + } + + const portPattern = this.getPort(this.pattern); + if (portPattern === '*') { + return true; + } else if (portPattern === '' || port === '') { + const protos = this.getProtocols(); + + if (protocol === 'https') { + return ( + protos.includes('https') && + ((port === '443' && portPattern === '') || + (port === '' && portPattern === '443') || + (port === '' && portPattern === '')) + ); + } else if (protocol === 'http') { + return ( + protos.includes('http') && + ((port === '80' && portPattern === '') || + (port === '' && portPattern === '80') || + (port === '' && portPattern === '')) + ); + } + } + + return portPattern === port; + } + + testProtocol(protocol: string) { + const protoPatterns = this.getProtocols(); + + for (const pattern of protoPatterns) { + if (pattern === '*') { + return true; + } else if (pattern === protocol) { + return true; + } + } + return false; + } + + override toString() { + return this.pattern; + } + + update(pattern: string) { + this.pattern = pattern; + } } export class UrlMatchPatternList extends PropertyList { - override _kind = 'UrlMatchPatternList'; + override _kind = 'UrlMatchPatternList'; - constructor(parent: PropertyList | undefined, populate: T[]) { - super(UrlMatchPattern, undefined, populate); - this.parent = parent; - } + constructor(parent: PropertyList | undefined, populate: T[]) { + super(UrlMatchPattern, undefined, populate); + this.parent = parent; + } - static isUrlMatchPatternList(obj: any) { - return '_kind' in obj && obj._kind === 'UrlMatchPatternList'; - } + static isUrlMatchPatternList(obj: any) { + return '_kind' in obj && obj._kind === 'UrlMatchPatternList'; + } - test(urlStr: string) { - return this - .filter(matchPattern => matchPattern.test(urlStr), {}) - .length > 0; - } + test(urlStr: string) { + return this.filter(matchPattern => matchPattern.test(urlStr), {}).length > 0; + } } export function toUrlObject(url: string | Url): Url { - if (!url) { - throw Error('Request URL is not specified'); - } - return typeof url === 'string' ? new Url(url) : url; + if (!url) { + throw Error('Request URL is not specified'); + } + return typeof url === 'string' ? new Url(url) : url; } diff --git a/packages/insomnia-scripting-environment/src/objects/utils.ts b/packages/insomnia-scripting-environment/src/objects/utils.ts index 2f0282ba89..f3f2c7aef3 100644 --- a/packages/insomnia-scripting-environment/src/objects/utils.ts +++ b/packages/insomnia-scripting-environment/src/objects/utils.ts @@ -1,6 +1,3 @@ export function checkIfUrlIncludesTag(url: string): boolean { - return /{%/.test(`${url}`) || - /%}/.test(`${url}`) || - /{{/.test(`${url}`) || - /}}/.test(`${url}`); + return /{%/.test(`${url}`) || /%}/.test(`${url}`) || /{{/.test(`${url}`) || /}}/.test(`${url}`); } diff --git a/packages/insomnia-scripting-environment/src/objects/variables.ts b/packages/insomnia-scripting-environment/src/objects/variables.ts index e4986bfffa..44bbbbf873 100644 --- a/packages/insomnia-scripting-environment/src/objects/variables.ts +++ b/packages/insomnia-scripting-environment/src/objects/variables.ts @@ -2,68 +2,64 @@ import { unsupportedError } from './properties'; import { Property, PropertyList } from './properties'; export interface VariableDefinition { - id?: string; - key: string; - name?: string; - value: string; - type?: string; - disabled?: boolean; + id?: string; + key: string; + name?: string; + value: string; + type?: string; + disabled?: boolean; } export class Variable extends Property { - key: string; - value: any; - type: string; - override _kind = 'Variable'; + key: string; + value: any; + type: string; + override _kind = 'Variable'; - constructor(def?: VariableDefinition) { - super(); + constructor(def?: VariableDefinition) { + super(); - this.id = def ? def.id || '' : ''; - this.key = def ? def.key : ''; - this.name = def ? def.name : ''; - this.value = def ? def.value : ''; - this.type = def && def.type ? def.type : 'Variable'; - this.disabled = def ? def.disabled : false; + this.id = def ? def.id || '' : ''; + this.key = def ? def.key : ''; + this.name = def ? def.name : ''; + this.value = def ? def.value : ''; + this.type = def && def.type ? def.type : 'Variable'; + this.disabled = def ? def.disabled : false; + } + + static override _index = 'key'; + + // unknown usage and unsupported + static types() { + throw unsupportedError('types'); + } + + // cast typecasts a value to the Variable.types of this Variable. + cast(value: any) { + if ('_kind' in value && value._kind === 'Variable') { + return value.value; } + return undefined; + } - static override _index = 'key'; + get() { + return this.value; + } - // unknown usage and unsupported - static types() { - throw unsupportedError('types'); - } - - // cast typecasts a value to the Variable.types of this Variable. - cast(value: any) { - if ('_kind' in value && value._kind === 'Variable') { - return value.value; - } - return undefined; - } - - get() { - return this.value; - } - - set(value: any) { - this.value = value; - } + set(value: any) { + this.value = value; + } } export class VariableList extends PropertyList { - override _kind = 'VariableList'; + override _kind = 'VariableList'; - constructor(parent: PropertyList | undefined, populate: T[]) { - super( - Variable, - undefined, - populate - ); - this.parent = parent; - } + constructor(parent: PropertyList | undefined, populate: T[]) { + super(Variable, undefined, populate); + this.parent = parent; + } - static isVariableList(obj: any) { - return '_kind' in obj && obj._kind === 'VariableList'; - } + static isVariableList(obj: any) { + return '_kind' in obj && obj._kind === 'VariableList'; + } } diff --git a/packages/insomnia-scripting-environment/tsconfig.json b/packages/insomnia-scripting-environment/tsconfig.json index 92caa82ffa..34e2332196 100644 --- a/packages/insomnia-scripting-environment/tsconfig.json +++ b/packages/insomnia-scripting-environment/tsconfig.json @@ -19,17 +19,8 @@ "verbatimModuleSyntax": true, "jsx": "react", /* If your code runs in the DOM: */ - "lib": [ - "es2023", - "dom", - "dom.iterable" - ], + "lib": ["es2023", "dom", "dom.iterable"] }, - "include": [ - "../insomnia/types" - ], - "exclude": [ - "**/__tests__", - "node_modules" - ] + "include": ["../insomnia/types"], + "exclude": ["**/__tests__", "node_modules"] } diff --git a/packages/insomnia-smoke-test/playwright/paths.ts b/packages/insomnia-smoke-test/playwright/paths.ts index c3dd6c0d76..ed81858003 100644 --- a/packages/insomnia-smoke-test/playwright/paths.ts +++ b/packages/insomnia-smoke-test/playwright/paths.ts @@ -38,19 +38,24 @@ const pathLookup: Record> = { }; let binaryPath: string; -const platformPath = pathLookup[process.platform] +const platformPath = pathLookup[process.platform]; if (typeof platformPath === 'string') { - binaryPath = platformPath + binaryPath = platformPath; } else if (process.arch in platformPath) { - binaryPath = platformPath[process.arch] + binaryPath = platformPath[process.arch]; } else { - throw new Error(`Cannot find binary path for ${process.platform} ${process.arch}`) + throw new Error(`Cannot find binary path for ${process.platform} ${process.arch}`); } export const cwd = path.resolve(__dirname, '..', '..', 'insomnia'); const repoRoot = path.resolve(__dirname, '..', '..', '..'); const insomniaBinary = path.join(cwd, 'dist', binaryPath); -const electronBinary = path.join(repoRoot, 'node_modules', '.bin', process.platform === 'win32' ? 'electron.cmd' : 'electron'); +const electronBinary = path.join( + repoRoot, + 'node_modules', + '.bin', + process.platform === 'win32' ? 'electron.cmd' : 'electron', +); export const executablePath = bundleType() === 'package' ? insomniaBinary : electronBinary; diff --git a/packages/insomnia-smoke-test/playwright/test.ts b/packages/insomnia-smoke-test/playwright/test.ts index c5d6b4f5d6..720bd2fa02 100644 --- a/packages/insomnia-smoke-test/playwright/test.ts +++ b/packages/insomnia-smoke-test/playwright/test.ts @@ -2,13 +2,7 @@ import { ElectronApplication, test as baseTest, TraceMode } from '@playwright/test'; import path from 'path'; -import { - bundleType, - cwd, - executablePath, - mainPath, - randomDataPath, -} from './paths'; +import { bundleType, cwd, executablePath, mainPath, randomDataPath } from './paths'; // Throw an error if the condition fails // > Not providing an inline default argument for message as the result is smaller @@ -97,7 +91,7 @@ export const test = baseTest.extend<{ INSOMNIA_VAULT_KEY: userConfig.vaultKey || '', INSOMNIA_VAULT_SALT: userConfig.vaultSalt || '', INSOMNIA_VAULT_SRP_SECRET: userConfig.vaultSrpSecret || '', - ...userConfig.session ? { INSOMNIA_SESSION: JSON.stringify(userConfig.session) } : {}, + ...(userConfig.session ? { INSOMNIA_SESSION: JSON.stringify(userConfig.session) } : {}), }; const electronApp = await playwright._electron.launch({ @@ -113,11 +107,15 @@ export const test = baseTest.extend<{ const appContext = electronApp.context(); - const traceMode: TraceMode = typeof trace === 'string' ? trace as TraceMode : trace.mode; + const traceMode: TraceMode = typeof trace === 'string' ? (trace as TraceMode) : trace.mode; const defaultTraceOptions = { screenshots: true, snapshots: true, sources: true }; - const traceOptions = typeof trace === 'string' ? defaultTraceOptions : { ...defaultTraceOptions, ...trace, mode: undefined }; - const captureTrace = (traceMode === 'on' || traceMode === 'retain-on-failure' || (traceMode === 'on-first-retry' && testInfo.retry === 1)); + const traceOptions = + typeof trace === 'string' ? defaultTraceOptions : { ...defaultTraceOptions, ...trace, mode: undefined }; + const captureTrace = + traceMode === 'on' || + traceMode === 'retain-on-failure' || + (traceMode === 'on-first-retry' && testInfo.retry === 1); if (captureTrace) { await appContext.tracing.start(traceOptions); @@ -132,7 +130,11 @@ export const test = baseTest.extend<{ } finally { // set testFailed to true if the test timed out or failed testFailed = testFailed || testInfo.status === 'timedOut' || testInfo.status === 'failed'; - if (traceMode === 'on' || (traceMode === 'retain-on-failure' && testFailed) || (traceMode === 'on-first-retry' && testInfo.retry === 1)) { + if ( + traceMode === 'on' || + (traceMode === 'retain-on-failure' && testFailed) || + (traceMode === 'on-first-retry' && testInfo.retry === 1) + ) { // Use a different name rather than the default trace.zip to avoid overwriting the trace. // Refer: https://github.com/microsoft/playwright/issues/35005 await appContext.tracing.stop({ @@ -153,46 +155,46 @@ export const test = baseTest.extend<{ await use(page); }, - dataPath: async ({ }, use) => { + dataPath: async ({}, use) => { const insomniaDataPath = randomDataPath(); await use(insomniaDataPath); }, - userConfig: async ({ }, use) => { + userConfig: async ({}, use) => { await use({ skipOnboarding: true, publicKey: 'txb/w8DASTpPQqeHE/hpI3ABKzit+pv5n2We5dbtYRo=', secretKey: 'Tb1QKsI3wVZxhS8TuQESHB2x7f68PzeTzTMmLpnnFVU=', code: 'BTxpIfgXY1VgUpoPpqA25RkCPGQ2MAkZsaY6IZ0bamd0WsYQlJM6iy8PV9hEHS1Gk96SBC6%2BM%2FGhv8IaVl1N6V5wdghHwU2sGKGkW%2Fevx1HiqAUsAqIry8aWRqAkc0n3KmW%2B%2F8lyeHCpy5jhsXqMMqXMbZh8dN1q%2ByRe2C6MJS1A706KbPUhI7PRi%2FsmK0TcNT7lgBKKHRVzPTvjpLcjgzSJFL4K%2BEzgY9Ue4gh0gPw89sM9dV%2F2sAlpw0LA7rF06NyoPhA%3D', session: { - 'id': 'sess_64a477e6b59d43a5a607f84b4f73e3ce', + id: 'sess_64a477e6b59d43a5a607f84b4f73e3ce', // Expire in 2077 - 'sessionExpiry': new Date(2147483647000), - 'publicKey': { - 'alg': 'RSA-OAEP-256', - 'e': 'AQAB', - 'ext': true, - 'key_ops': ['encrypt'], - 'kty': 'RSA', - 'n': 'pTQVaUaiqggIldSKm6ib6eFRLLoGj9W-2O4gTbiorR-2b8-ZmKUwQ0F-jgYX71AjYaFn5VjOHOHSP6byNAjN7WzJ6A_Z3tytNraLoZfwK8KdfflOCZiZzQeD3nO8BNgh_zEgCHStU61b6N6bSpCKjbyPkmZcOkJfsz0LJMAxrXvFB-I42WYA2vJKReTJKXeYx4d6L_XGNIoYtmGZit8FldT4AucfQUXgdlKvr4_OZmt6hgjwt_Pjcu-_jO7m589mMWMebfUhjte3Lp1jps0MqTOvgRb0FQf5eoBHnL01OZjvFPDKeqlvoz7II9wFNHIKzSvgAKnyemh6DiyPuIukyQ', + sessionExpiry: new Date(2147483647000), + publicKey: { + alg: 'RSA-OAEP-256', + e: 'AQAB', + ext: true, + key_ops: ['encrypt'], + kty: 'RSA', + n: 'pTQVaUaiqggIldSKm6ib6eFRLLoGj9W-2O4gTbiorR-2b8-ZmKUwQ0F-jgYX71AjYaFn5VjOHOHSP6byNAjN7WzJ6A_Z3tytNraLoZfwK8KdfflOCZiZzQeD3nO8BNgh_zEgCHStU61b6N6bSpCKjbyPkmZcOkJfsz0LJMAxrXvFB-I42WYA2vJKReTJKXeYx4d6L_XGNIoYtmGZit8FldT4AucfQUXgdlKvr4_OZmt6hgjwt_Pjcu-_jO7m589mMWMebfUhjte3Lp1jps0MqTOvgRb0FQf5eoBHnL01OZjvFPDKeqlvoz7II9wFNHIKzSvgAKnyemh6DiyPuIukyQ', }, - 'encPrivateKey': { - 'iv': '3a1f2bdb8acbf15f469d57a2', - 't': '904d6b1bc0ece8e5df6fefb9efefda7c', - 'd': '2a7b0c4beb773fa3e3c2158f0bfa654a88c4041184c3b1e01b4ddd2da2c647244a0d66d258b6abb6a9385251bf5d79e6b03ef35bdfafcb400547f8f88adb8bceb7020f2d873d5a74fb5fc561e7bd67cea0a37c49107bf5c96631374dc44ddb1e4a8b5688dc6560fc6143294ed92c3ad8e1696395dfdf15975aa67b9212366dbfcb31191e4f4fe3559c89a92fb1f0f1cc6cbf90d8a062307fce6e7701f6f5169d9247c56dae79b55fba1e10fde562b971ca708c9a4d87e6e9d9e890b88fa0480360420e610c4e41459570e52ae72f349eadf84fc0a68153722de3280becf8a1762e7faebe964f0ad706991c521feda3440d3e1b22f2c221a80490359879bd47c0d059ace81213c74a1e192dbebd8a80cf58c9eb1fe461a971b88d3899baf4c4ef7141623c93fb4a54758f5e1cf9ee35cd00777fa89b24e4ded57219e770de2670619c6e971935c61ae72e3276cf8db49dfa0e91c68222f02d7e0c69b399af505de7e5a90852d83e0a30934b0362db986f3aaefaaf1a96fef3e8165287a3a7f0ee1e072d9dee3aefb86194e1d877d6b34529d45a70ec4573c35a7fe27833c77c3154b0ad02187e4fcecd408bcf4b29a85a5dc358cb479140f4983fcd936141f581764669651530af97d2b7d9416aea7de67e787f3e29ae3eba6672bcd934dc1e308783aa63a4ab46d48d213cf53ad6bd8828011f5bfa3aa5ee24551c694e829b54c93b1dda6c3ddda04756d68a28bec8d044c8af4147680dc5b972d0ca74299b0ab6306b9e7b99bf0557558df120455a272145b7aa792654730f3d670b76d72408f5ce1cf5fbd453d2903fa72cf26397437854ba8abbb731a8107f6a86a01fa98edc81bb42a4c1330f779e7a0fbd1820eaed78e03e40a996e03884b707556be06fd14ee8f4035469210d1d2bb8f58285fc2ab6de3d3cc0e4e1f40c6d9d24b50dc8e2e2374a0aff52031b3736c2982133bb19dd551ce1f953f4ba02b0cf53382c15752e202c138cb42b2322df103ff17fd886dfd5f992b711673cdf16048c4bff19038138b161c2e1783b85fc7b965a91ac4795fcbfebf827940cacdeae57946863aee027df43b36612f3cb8f34dc44396e87c564bf10f5b1a9dfbd6da3d7f4f65024b0b4f8ce51d01c230840941fc4523b17eb1c2522032f410e8328239a11a15ab755c32945ce52966d5bfb4666909ed2ca04d536e4bf92091563dd44d46cbb35e53c2481400058ab3b52a0280d262551073f61db125ee280e2cc1ec0bdf9c4817824261465011e34c2296411384f7f5e16742157c5520f137631edf498aa39c7c32b107e3634cbeb70feea19a233c8bd939d665135c9f7c1bb33cb47edc58bdbbcde9b0b9eb73a46642e4639289a62638fb7813e1eeaadd105c803de8357236f33c4bcf31a876b5867591af8f165eba0b35cf0b0886af17dab35a6a39f8f576387d6ffb9e677ee46fc0f11ff069a2a068fce441ff8f4125095fad228c2bf45c788d641941ed13c0a16fffcafd7c7eff11bb7550c0b7d54eebdbd2066e3bbdb47aaee2b5f1e499726324a40015458c7de1db0abe872594d8e6802deff7ea9518bdb3a3e46f07139267fd67dc570ba8ab04c2b37ce6a34ec73b802c7052a2eef0cae1b0979322ef86395535db80cf2a9a88aa7c2e5cc28a93612a8dafe1982f741d7cec28a866f6c09dba5b99ead24c3df0ca03c6c5afae41f3d39608a8f49b0d6a0b541a159409791c25ede103eb4f79cfbd0cc9c9aa6b591755c1e9fd07b5b9e38ed85b5939e65d127256f6a4c078f8c9d655c4f072f9cbcfb2e1e17eaa83dc62aaab2a6dc3735ee76ce7a215740f795f1fbe7136c7734ae3714438015e8fc383d63775a8abddb23cbc5f906c046bb0b5b31d492a7c151b40ea82c7c966e25820641c55b343b89d6378f90de5983fa76547e9d6c634effdf019a0fd9b6d3e488a5aa94f0710d517ba4f7c1ed82f9f3072612e953e036c0ec7f3c618368362f6da6f3af76056a66aef914805cc8b628f1c11695f760b535ded9ff66727273ae7e12d67a01243d75f22fec8ed1b043122a211c923aa92ecbbe01dd0d7195c3c0e09a2a6ab3eca354963122d5a0ec16e2b2b81b0ddce6ec0a312c492a96a4fd392f1deb6a1f3318541a3f87e5c9e73ee7edd3b855910f412789e25038108e1eaae04dcfb02b4d958c00c630dc8caa87a40798ce7156d2ade882e68832d39fe8f9bce6a995249a7383013a5093c4af55c3b7232de0f2593d82c30b8dabd0784455037f25f6bb66a6d0d8f72bc7be0dee2d0a8af44bb4e143257d873268d331722c3253ea5c004e72daf04c875e2054f2b4b2bca2979fd046a1e835600045edf2f159d851a540a91a1ab8fbcb64594d21942bbaa2160535d32496ba7ce4a76c6bdeb9bb4c5cab7bed1ae26564058d0be125803d7019b83b3953c4b0cc1f8299c4edcf6a5faa4765092412d368b277689900e71fb5d47581057adaa2dd494e0f66dc1aa16f3741973b0d9ffa1728aeafab84b777394a7afae0f8eabaa6b740f1c60ca26469f0c9356ec880ad6f4dc01b99bd14d7a4bb8afc97662a9e68b0155e4cdf3caa3402819ac6ce562c8fe06edb50a31cfd7a', - 'ad': '', + encPrivateKey: { + iv: '3a1f2bdb8acbf15f469d57a2', + t: '904d6b1bc0ece8e5df6fefb9efefda7c', + d: '2a7b0c4beb773fa3e3c2158f0bfa654a88c4041184c3b1e01b4ddd2da2c647244a0d66d258b6abb6a9385251bf5d79e6b03ef35bdfafcb400547f8f88adb8bceb7020f2d873d5a74fb5fc561e7bd67cea0a37c49107bf5c96631374dc44ddb1e4a8b5688dc6560fc6143294ed92c3ad8e1696395dfdf15975aa67b9212366dbfcb31191e4f4fe3559c89a92fb1f0f1cc6cbf90d8a062307fce6e7701f6f5169d9247c56dae79b55fba1e10fde562b971ca708c9a4d87e6e9d9e890b88fa0480360420e610c4e41459570e52ae72f349eadf84fc0a68153722de3280becf8a1762e7faebe964f0ad706991c521feda3440d3e1b22f2c221a80490359879bd47c0d059ace81213c74a1e192dbebd8a80cf58c9eb1fe461a971b88d3899baf4c4ef7141623c93fb4a54758f5e1cf9ee35cd00777fa89b24e4ded57219e770de2670619c6e971935c61ae72e3276cf8db49dfa0e91c68222f02d7e0c69b399af505de7e5a90852d83e0a30934b0362db986f3aaefaaf1a96fef3e8165287a3a7f0ee1e072d9dee3aefb86194e1d877d6b34529d45a70ec4573c35a7fe27833c77c3154b0ad02187e4fcecd408bcf4b29a85a5dc358cb479140f4983fcd936141f581764669651530af97d2b7d9416aea7de67e787f3e29ae3eba6672bcd934dc1e308783aa63a4ab46d48d213cf53ad6bd8828011f5bfa3aa5ee24551c694e829b54c93b1dda6c3ddda04756d68a28bec8d044c8af4147680dc5b972d0ca74299b0ab6306b9e7b99bf0557558df120455a272145b7aa792654730f3d670b76d72408f5ce1cf5fbd453d2903fa72cf26397437854ba8abbb731a8107f6a86a01fa98edc81bb42a4c1330f779e7a0fbd1820eaed78e03e40a996e03884b707556be06fd14ee8f4035469210d1d2bb8f58285fc2ab6de3d3cc0e4e1f40c6d9d24b50dc8e2e2374a0aff52031b3736c2982133bb19dd551ce1f953f4ba02b0cf53382c15752e202c138cb42b2322df103ff17fd886dfd5f992b711673cdf16048c4bff19038138b161c2e1783b85fc7b965a91ac4795fcbfebf827940cacdeae57946863aee027df43b36612f3cb8f34dc44396e87c564bf10f5b1a9dfbd6da3d7f4f65024b0b4f8ce51d01c230840941fc4523b17eb1c2522032f410e8328239a11a15ab755c32945ce52966d5bfb4666909ed2ca04d536e4bf92091563dd44d46cbb35e53c2481400058ab3b52a0280d262551073f61db125ee280e2cc1ec0bdf9c4817824261465011e34c2296411384f7f5e16742157c5520f137631edf498aa39c7c32b107e3634cbeb70feea19a233c8bd939d665135c9f7c1bb33cb47edc58bdbbcde9b0b9eb73a46642e4639289a62638fb7813e1eeaadd105c803de8357236f33c4bcf31a876b5867591af8f165eba0b35cf0b0886af17dab35a6a39f8f576387d6ffb9e677ee46fc0f11ff069a2a068fce441ff8f4125095fad228c2bf45c788d641941ed13c0a16fffcafd7c7eff11bb7550c0b7d54eebdbd2066e3bbdb47aaee2b5f1e499726324a40015458c7de1db0abe872594d8e6802deff7ea9518bdb3a3e46f07139267fd67dc570ba8ab04c2b37ce6a34ec73b802c7052a2eef0cae1b0979322ef86395535db80cf2a9a88aa7c2e5cc28a93612a8dafe1982f741d7cec28a866f6c09dba5b99ead24c3df0ca03c6c5afae41f3d39608a8f49b0d6a0b541a159409791c25ede103eb4f79cfbd0cc9c9aa6b591755c1e9fd07b5b9e38ed85b5939e65d127256f6a4c078f8c9d655c4f072f9cbcfb2e1e17eaa83dc62aaab2a6dc3735ee76ce7a215740f795f1fbe7136c7734ae3714438015e8fc383d63775a8abddb23cbc5f906c046bb0b5b31d492a7c151b40ea82c7c966e25820641c55b343b89d6378f90de5983fa76547e9d6c634effdf019a0fd9b6d3e488a5aa94f0710d517ba4f7c1ed82f9f3072612e953e036c0ec7f3c618368362f6da6f3af76056a66aef914805cc8b628f1c11695f760b535ded9ff66727273ae7e12d67a01243d75f22fec8ed1b043122a211c923aa92ecbbe01dd0d7195c3c0e09a2a6ab3eca354963122d5a0ec16e2b2b81b0ddce6ec0a312c492a96a4fd392f1deb6a1f3318541a3f87e5c9e73ee7edd3b855910f412789e25038108e1eaae04dcfb02b4d958c00c630dc8caa87a40798ce7156d2ade882e68832d39fe8f9bce6a995249a7383013a5093c4af55c3b7232de0f2593d82c30b8dabd0784455037f25f6bb66a6d0d8f72bc7be0dee2d0a8af44bb4e143257d873268d331722c3253ea5c004e72daf04c875e2054f2b4b2bca2979fd046a1e835600045edf2f159d851a540a91a1ab8fbcb64594d21942bbaa2160535d32496ba7ce4a76c6bdeb9bb4c5cab7bed1ae26564058d0be125803d7019b83b3953c4b0cc1f8299c4edcf6a5faa4765092412d368b277689900e71fb5d47581057adaa2dd494e0f66dc1aa16f3741973b0d9ffa1728aeafab84b777394a7afae0f8eabaa6b740f1c60ca26469f0c9356ec880ad6f4dc01b99bd14d7a4bb8afc97662a9e68b0155e4cdf3caa3402819ac6ce562c8fe06edb50a31cfd7a', + ad: '', }, - 'symmetricKey': { - 'alg': 'A256GCM', - 'ext': true, - 'k': 'w62OJNWF4G8iWA8ZrTpModiY8dICyHI7ko1vMLb877g=', - 'key_ops': ['encrypt', 'decrypt'], - 'kty': 'oct', + symmetricKey: { + alg: 'A256GCM', + ext: true, + k: 'w62OJNWF4G8iWA8ZrTpModiY8dICyHI7ko1vMLb877g=', + key_ops: ['encrypt', 'decrypt'], + kty: 'oct', }, - 'email': 'insomnia-user@konghq.com', - 'accountId': 'acct_64a477e6b59d43a5a607f84b4f73e3ce', - 'firstName': 'Rick', - 'lastName': 'Morty', + email: 'insomnia-user@konghq.com', + accountId: 'acct_64a477e6b59d43a5a607f84b4f73e3ce', + firstName: 'Rick', + lastName: 'Morty', }, }); }, diff --git a/packages/insomnia-smoke-test/server/basic-auth.ts b/packages/insomnia-smoke-test/server/basic-auth.ts index 4f6c6874a5..c0d61066ef 100644 --- a/packages/insomnia-smoke-test/server/basic-auth.ts +++ b/packages/insomnia-smoke-test/server/basic-auth.ts @@ -15,8 +15,5 @@ export const basicAuthRouter = express.Router(); basicAuthRouter.use(basicAuth({ users })); basicAuthRouter.get('/', (_, res) => { - res - .status(200) - .header('content-type', 'text/plain') - .send('basic auth received'); + res.status(200).header('content-type', 'text/plain').send('basic auth received'); }); diff --git a/packages/insomnia-smoke-test/server/github-api.ts b/packages/insomnia-smoke-test/server/github-api.ts index 280b7646e0..441eb7bc97 100644 --- a/packages/insomnia-smoke-test/server/github-api.ts +++ b/packages/insomnia-smoke-test/server/github-api.ts @@ -36,13 +36,13 @@ export default (app: Application) => { app.post('/v1/oauth/github', (_req, res) => { res.status(200).send({ - 'access_token': '123456789', + access_token: '123456789', }); }); app.post('/v1/oauth/github-app', (_req, res) => { res.status(200).send({ - 'access_token': '123456789', + access_token: '123456789', }); }); }; diff --git a/packages/insomnia-smoke-test/server/gitlab-api.ts b/packages/insomnia-smoke-test/server/gitlab-api.ts index 8396721585..4ea2599bf9 100644 --- a/packages/insomnia-smoke-test/server/gitlab-api.ts +++ b/packages/insomnia-smoke-test/server/gitlab-api.ts @@ -3,11 +3,11 @@ import type { Application } from 'express'; export default (app: Application) => { app.post('/gitlab-api/api/graphql', (_req, res) => { res.status(200).send({ - 'data': { - 'currentUser': { - 'publicEmail': null, - 'name': 'Mark Kim', - 'avatarUrl': null, + data: { + currentUser: { + publicEmail: null, + name: 'Mark Kim', + avatarUrl: null, }, }, }); @@ -22,18 +22,18 @@ export default (app: Application) => { app.post('/gitlab-api/oauth/token', (_req, res) => { res.status(200).send({ - 'access_token': '123456789', - 'created_at': 1652246628, - 'expires_in': 6955, - 'refresh_token': '1234567891', + access_token: '123456789', + created_at: 1652246628, + expires_in: 6955, + refresh_token: '1234567891', scope: 'api read_user write_repository read_repository email', - 'token_type': 'Bearer', + token_type: 'Bearer', }); }); app.post('/gitlab-api/oauth/authorize', (_req, res) => { res.status(200).send({ - 'access_token': '123456789', + access_token: '123456789', }); }); }; diff --git a/packages/insomnia-smoke-test/server/graphql.ts b/packages/insomnia-smoke-test/server/graphql.ts index 7137ebe900..76b8241657 100644 --- a/packages/insomnia-smoke-test/server/graphql.ts +++ b/packages/insomnia-smoke-test/server/graphql.ts @@ -1,4 +1,11 @@ -import { GraphQLEnumType, GraphQLInputObjectType, GraphQLInt, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql'; +import { + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInt, + GraphQLObjectType, + GraphQLSchema, + GraphQLString, +} from 'graphql'; const TypeVars = new GraphQLObjectType({ name: 'Vars', @@ -47,14 +54,14 @@ export const schema = new GraphQLSchema({ echoNum: { type: GraphQLInt, args: { - 'intVar': { type: GraphQLInt }, + intVar: { type: GraphQLInt }, }, resolve: () => 777, }, echoVars: { type: TypeVars, args: { - 'vars': { type: InputVars }, + vars: { type: InputVars }, }, resolve: vars => vars, }, diff --git a/packages/insomnia-smoke-test/server/grpc.ts b/packages/insomnia-smoke-test/server/grpc.ts index 2b63dc12e3..b15c790664 100644 --- a/packages/insomnia-smoke-test/server/grpc.ts +++ b/packages/insomnia-smoke-test/server/grpc.ts @@ -9,15 +9,13 @@ import fs from 'fs'; import path from 'path'; const PROTO_PATH = path.resolve('../../packages/insomnia/src/network/grpc/__fixtures__/library/route_guide.proto'); -const packageDefinition = protoLoader.loadSync( - PROTO_PATH, - { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - }); +const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, +}); const routeguide = grpc.loadPackageDefinition(packageDefinition).routeguide; @@ -38,8 +36,7 @@ function checkFeature(point: { latitude: any; longitude: any }) { // Check if there is already a feature object for the given point for (let i = 0; i < featureList.length; i++) { feature = featureList[i]; - if (feature.location.latitude === point.latitude && - feature.location.longitude === point.longitude) { + if (feature.location.latitude === point.latitude && feature.location.longitude === point.longitude) { return feature; } } @@ -71,14 +68,16 @@ const listFeatures: HandleCall = (call: any) => { const top = Math.max(lo.latitude, hi.latitude); const bottom = Math.min(lo.latitude, hi.latitude); // For each feature, check if it is in the given bounding box - featureList.forEach(function(feature) { + featureList.forEach(function (feature) { if (feature.name === '') { return; } - if (feature.location.longitude >= left && - feature.location.longitude <= right && - feature.location.latitude >= bottom && - feature.location.latitude <= top) { + if ( + feature.location.longitude >= left && + feature.location.longitude <= right && + feature.location.latitude >= bottom && + feature.location.latitude <= top + ) { call.write(feature); } }); @@ -94,9 +93,9 @@ const listFeatures: HandleCall = (call: any) => { */ function getDistance(start: { latitude: number; longitude: number }, end: { latitude: number; longitude: number }) { function toRadians(num: number) { - return num * Math.PI / 180; + return (num * Math.PI) / 180; } - const R = 6371000; // earth radius in metres + const R = 6371000; // earth radius in metres const lat1 = toRadians(start.latitude / COORD_FACTOR); const lat2 = toRadians(end.latitude / COORD_FACTOR); const lon1 = toRadians(start.longitude / COORD_FACTOR); @@ -104,9 +103,9 @@ function getDistance(start: { latitude: number; longitude: number }, end: { lati const deltalat = lat2 - lat1; const deltalon = lon2 - lon1; - const a = Math.sin(deltalat / 2) * Math.sin(deltalat / 2) + - Math.cos(lat1) * Math.cos(lat2) * - Math.sin(deltalon / 2) * Math.sin(deltalon / 2); + const a = + Math.sin(deltalat / 2) * Math.sin(deltalat / 2) + + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltalon / 2) * Math.sin(deltalon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } @@ -121,7 +120,7 @@ const recordRoute: HandleCall = (call: any, callback: any) => { let previous: { latitude: number; longitude: number } | null = null; // Start a timer const startTime = process.hrtime(); - call.on('data', function(point: any) { + call.on('data', function (point: any) { pointCount += 1; if (checkFeature(point).name !== '') { featureCount += 1; @@ -133,7 +132,7 @@ const recordRoute: HandleCall = (call: any, callback: any) => { } previous = point; }); - call.on('end', function() { + call.on('end', function () { callback(null, { point_count: pointCount, feature_count: featureCount, @@ -162,12 +161,12 @@ function pointKey(point: { latitude: string; longitude: string }) { * @param {Duplex} call The stream for incoming and outgoing messages */ const routeChat: HandleCall = (call: any) => { - call.on('data', function(note: any) { + call.on('data', function (note: any) { const key = pointKey(note.location); /* For each note sent, respond with all previous notes that correspond to * the same point */ if (routeNotes.hasOwnProperty(key)) { - routeNotes[key].forEach(function(note: any) { + routeNotes[key].forEach(function (note: any) { call.write(note); }); } else { @@ -176,7 +175,7 @@ const routeChat: HandleCall = (call: any) => { // Then add the new note to the list routeNotes[key].push(JSON.parse(JSON.stringify(note))); }); - call.on('end', function() { + call.on('end', function () { call.end(); }); }; @@ -204,7 +203,7 @@ export const startGRPCServer = (port: number) => { } const dbPath = '../../packages/insomnia/src/network/grpc/__fixtures__/library/route_guide_db.json'; - fs.readFile(path.resolve(dbPath), function(err, data) { + fs.readFile(path.resolve(dbPath), function (err, data) { if (err) { throw err; } @@ -238,7 +237,7 @@ export const startGRPCServer = (port: number) => { private_key: serverKey, }, ], - true // mTLS enabled, temporarily change to false for local testing if needed + true, // mTLS enabled, temporarily change to false for local testing if needed ); serverWithTLS.bindAsync('localhost:50052', serverCredentials, error => { if (error) { diff --git a/packages/insomnia-smoke-test/server/index.ts b/packages/insomnia-smoke-test/server/index.ts index d4ba7eabf7..a7aca2dbb9 100644 --- a/packages/insomnia-smoke-test/server/index.ts +++ b/packages/insomnia-smoke-test/server/index.ts @@ -46,7 +46,7 @@ async function echoHandler(req: any, res: any) { data: req.body.toString(), cookies: req.cookies, }); -}; +} app.get('/echo', rawParser, echoHandler); app.post('/echo', rawParser, echoHandler); @@ -123,20 +123,27 @@ app.post('/send-event', (request, response) => { response.json({ success: true }); }); -startWebSocketServer(app.listen(port, () => { - console.log(`Listening at http://localhost:${port}`); - console.log(`Listening at ws://localhost:${port}`); -})); +startWebSocketServer( + app.listen(port, () => { + console.log(`Listening at http://localhost:${port}`); + console.log(`Listening at ws://localhost:${port}`); + }), +); -startWebSocketServer(createServer({ - cert: readFileSync(join(__dirname, '../fixtures/certificates/localhost.pem')), - key: readFileSync(join(__dirname, '../fixtures/certificates/localhost-key.pem')), - ca: readFileSync(join(__dirname, '../fixtures/certificates/rootCA.pem')), - requestCert: true, - rejectUnauthorized: false, -}, app).listen(httpsPort, () => { - console.log(`Listening at https://localhost:${httpsPort}`); - console.log(`Listening at wss://localhost:${httpsPort}`); -})); +startWebSocketServer( + createServer( + { + cert: readFileSync(join(__dirname, '../fixtures/certificates/localhost.pem')), + key: readFileSync(join(__dirname, '../fixtures/certificates/localhost-key.pem')), + ca: readFileSync(join(__dirname, '../fixtures/certificates/rootCA.pem')), + requestCert: true, + rejectUnauthorized: false, + }, + app, + ).listen(httpsPort, () => { + console.log(`Listening at https://localhost:${httpsPort}`); + console.log(`Listening at wss://localhost:${httpsPort}`); + }), +); startGRPCServer(grpcPort); diff --git a/packages/insomnia-smoke-test/server/insomnia-api.ts b/packages/insomnia-smoke-test/server/insomnia-api.ts index 8ace4791f5..7d342b4133 100644 --- a/packages/insomnia-smoke-test/server/insomnia-api.ts +++ b/packages/insomnia-smoke-test/server/insomnia-api.ts @@ -14,47 +14,49 @@ const currentPlan = { type: 'team', }; -const projectsByOrgId = new Map(Object.entries({ - 'org_7ef19d06-5a24-47ca-bc81-3dea011edec2': [ - { - id: 'proj_org_7ef19d06-5a24-47ca-bc81-3dea011edec2', - name: 'Personal Workspace', - }, - ], - 'team_195a6ce0edb1427eb2e8ba7b986072e4': [ - { - id: 'proj_team_195a6ce0edb1427eb2e8ba7b986072e4', - name: 'Personal Workspace', - }, - ], -})); +const projectsByOrgId = new Map( + Object.entries({ + 'org_7ef19d06-5a24-47ca-bc81-3dea011edec2': [ + { + id: 'proj_org_7ef19d06-5a24-47ca-bc81-3dea011edec2', + name: 'Personal Workspace', + }, + ], + 'team_195a6ce0edb1427eb2e8ba7b986072e4': [ + { + id: 'proj_team_195a6ce0edb1427eb2e8ba7b986072e4', + name: 'Personal Workspace', + }, + ], + }), +); const organizations = [ // Personal organization { - 'id': 'org_7ef19d06-5a24-47ca-bc81-3dea011edec2', - 'name': 'feb56ab4b19347c4b648c99bfa7db363', - 'display_name': 'Personal workspace', - 'branding': { - 'logo_url': '', + id: 'org_7ef19d06-5a24-47ca-bc81-3dea011edec2', + name: 'feb56ab4b19347c4b648c99bfa7db363', + display_name: 'Personal workspace', + branding: { + logo_url: '', }, - 'metadata': { - 'organizationType': 'personal', - 'ownerAccountId': 'acct_64a477e6b59d43a5a607f84b4f73e3ce', + metadata: { + organizationType: 'personal', + ownerAccountId: 'acct_64a477e6b59d43a5a607f84b4f73e3ce', }, }, // Team Organization: Was a team before the migration now looks like this: // Teams migrated to Organizations have an id that starts with team_ and the team id is the same as the organization id { - 'id': 'team_195a6ce0edb1427eb2e8ba7b986072e4', - 'name': '07df6d95b60e4593af0424c74d96637a-team', - 'display_name': '🦄 Magic', - 'branding': { - 'logo_url': '', + id: 'team_195a6ce0edb1427eb2e8ba7b986072e4', + name: '07df6d95b60e4593af0424c74d96637a-team', + display_name: '🦄 Magic', + branding: { + logo_url: '', }, - 'metadata': { - 'organizationType': 'team', - 'ownerAccountId': 'acct_64a477e6b59d43a5a607f84b4f73e3ce', + metadata: { + organizationType: 'team', + ownerAccountId: 'acct_64a477e6b59d43a5a607f84b4f73e3ce', }, }, ]; @@ -68,72 +70,72 @@ const organizationFeatures = { }; const user = { - 'id': 'email|64f0dd619ab0786da330d83a', - 'email': 'insomnia-user@konghq.com', - 'name': 'Rick Morty', - 'picture': '', - 'bio': 'My BIO', - 'github': '', - 'linkedin': '', - 'twitter': '', - 'identities': null, - 'given_name': '', - 'family_name': '', + id: 'email|64f0dd619ab0786da330d83a', + email: 'insomnia-user@konghq.com', + name: 'Rick Morty', + picture: '', + bio: 'My BIO', + github: '', + linkedin: '', + twitter: '', + identities: null, + given_name: '', + family_name: '', }; const whoami = { - 'sessionExpiry': 4838400, - 'publicKey': { - 'alg': 'RSA-OAEP-256', - 'e': 'AQAB', - 'ext': true, - 'key_ops': ['encrypt'], - 'kty': 'RSA', - 'n': 'pTQVaUaiqggIldSKm6ib6eFRLLoGj9W-2O4gTbiorR-2b8-ZmKUwQ0F-jgYX71AjYaFn5VjOHOHSP6byNAjN7WzJ6A_Z3tytNraLoZfwK8KdfflOCZiZzQeD3nO8BNgh_zEgCHStU61b6N6bSpCKjbyPkmZcOkJfsz0LJMAxrXvFB-I42WYA2vJKReTJKXeYx4d6L_XGNIoYtmGZit8FldT4AucfQUXgdlKvr4_OZmt6hgjwt_Pjcu-_jO7m589mMWMebfUhjte3Lp1jps0MqTOvgRb0FQf5eoBHnL01OZjvFPDKeqlvoz7II9wFNHIKzSvgAKnyemh6DiyPuIukyQ', + sessionExpiry: 4838400, + publicKey: { + alg: 'RSA-OAEP-256', + e: 'AQAB', + ext: true, + key_ops: ['encrypt'], + kty: 'RSA', + n: 'pTQVaUaiqggIldSKm6ib6eFRLLoGj9W-2O4gTbiorR-2b8-ZmKUwQ0F-jgYX71AjYaFn5VjOHOHSP6byNAjN7WzJ6A_Z3tytNraLoZfwK8KdfflOCZiZzQeD3nO8BNgh_zEgCHStU61b6N6bSpCKjbyPkmZcOkJfsz0LJMAxrXvFB-I42WYA2vJKReTJKXeYx4d6L_XGNIoYtmGZit8FldT4AucfQUXgdlKvr4_OZmt6hgjwt_Pjcu-_jO7m589mMWMebfUhjte3Lp1jps0MqTOvgRb0FQf5eoBHnL01OZjvFPDKeqlvoz7II9wFNHIKzSvgAKnyemh6DiyPuIukyQ', }, - 'encPrivateKey': { - 'iv': '3a1f2bdb8acbf15f469d57a2', - 't': '904d6b1bc0ece8e5df6fefb9efefda7c', - 'd': '2a7b0c4beb773fa3e3c2158f0bfa654a88c4041184c3b1e01b4ddd2da2c647244a0d66d258b6abb6a9385251bf5d79e6b03ef35bdfafcb400547f8f88adb8bceb7020f2d873d5a74fb5fc561e7bd67cea0a37c49107bf5c96631374dc44ddb1e4a8b5688dc6560fc6143294ed92c3ad8e1696395dfdf15975aa67b9212366dbfcb31191e4f4fe3559c89a92fb1f0f1cc6cbf90d8a062307fce6e7701f6f5169d9247c56dae79b55fba1e10fde562b971ca708c9a4d87e6e9d9e890b88fa0480360420e610c4e41459570e52ae72f349eadf84fc0a68153722de3280becf8a1762e7faebe964f0ad706991c521feda3440d3e1b22f2c221a80490359879bd47c0d059ace81213c74a1e192dbebd8a80cf58c9eb1fe461a971b88d3899baf4c4ef7141623c93fb4a54758f5e1cf9ee35cd00777fa89b24e4ded57219e770de2670619c6e971935c61ae72e3276cf8db49dfa0e91c68222f02d7e0c69b399af505de7e5a90852d83e0a30934b0362db986f3aaefaaf1a96fef3e8165287a3a7f0ee1e072d9dee3aefb86194e1d877d6b34529d45a70ec4573c35a7fe27833c77c3154b0ad02187e4fcecd408bcf4b29a85a5dc358cb479140f4983fcd936141f581764669651530af97d2b7d9416aea7de67e787f3e29ae3eba6672bcd934dc1e308783aa63a4ab46d48d213cf53ad6bd8828011f5bfa3aa5ee24551c694e829b54c93b1dda6c3ddda04756d68a28bec8d044c8af4147680dc5b972d0ca74299b0ab6306b9e7b99bf0557558df120455a272145b7aa792654730f3d670b76d72408f5ce1cf5fbd453d2903fa72cf26397437854ba8abbb731a8107f6a86a01fa98edc81bb42a4c1330f779e7a0fbd1820eaed78e03e40a996e03884b707556be06fd14ee8f4035469210d1d2bb8f58285fc2ab6de3d3cc0e4e1f40c6d9d24b50dc8e2e2374a0aff52031b3736c2982133bb19dd551ce1f953f4ba02b0cf53382c15752e202c138cb42b2322df103ff17fd886dfd5f992b711673cdf16048c4bff19038138b161c2e1783b85fc7b965a91ac4795fcbfebf827940cacdeae57946863aee027df43b36612f3cb8f34dc44396e87c564bf10f5b1a9dfbd6da3d7f4f65024b0b4f8ce51d01c230840941fc4523b17eb1c2522032f410e8328239a11a15ab755c32945ce52966d5bfb4666909ed2ca04d536e4bf92091563dd44d46cbb35e53c2481400058ab3b52a0280d262551073f61db125ee280e2cc1ec0bdf9c4817824261465011e34c2296411384f7f5e16742157c5520f137631edf498aa39c7c32b107e3634cbeb70feea19a233c8bd939d665135c9f7c1bb33cb47edc58bdbbcde9b0b9eb73a46642e4639289a62638fb7813e1eeaadd105c803de8357236f33c4bcf31a876b5867591af8f165eba0b35cf0b0886af17dab35a6a39f8f576387d6ffb9e677ee46fc0f11ff069a2a068fce441ff8f4125095fad228c2bf45c788d641941ed13c0a16fffcafd7c7eff11bb7550c0b7d54eebdbd2066e3bbdb47aaee2b5f1e499726324a40015458c7de1db0abe872594d8e6802deff7ea9518bdb3a3e46f07139267fd67dc570ba8ab04c2b37ce6a34ec73b802c7052a2eef0cae1b0979322ef86395535db80cf2a9a88aa7c2e5cc28a93612a8dafe1982f741d7cec28a866f6c09dba5b99ead24c3df0ca03c6c5afae41f3d39608a8f49b0d6a0b541a159409791c25ede103eb4f79cfbd0cc9c9aa6b591755c1e9fd07b5b9e38ed85b5939e65d127256f6a4c078f8c9d655c4f072f9cbcfb2e1e17eaa83dc62aaab2a6dc3735ee76ce7a215740f795f1fbe7136c7734ae3714438015e8fc383d63775a8abddb23cbc5f906c046bb0b5b31d492a7c151b40ea82c7c966e25820641c55b343b89d6378f90de5983fa76547e9d6c634effdf019a0fd9b6d3e488a5aa94f0710d517ba4f7c1ed82f9f3072612e953e036c0ec7f3c618368362f6da6f3af76056a66aef914805cc8b628f1c11695f760b535ded9ff66727273ae7e12d67a01243d75f22fec8ed1b043122a211c923aa92ecbbe01dd0d7195c3c0e09a2a6ab3eca354963122d5a0ec16e2b2b81b0ddce6ec0a312c492a96a4fd392f1deb6a1f3318541a3f87e5c9e73ee7edd3b855910f412789e25038108e1eaae04dcfb02b4d958c00c630dc8caa87a40798ce7156d2ade882e68832d39fe8f9bce6a995249a7383013a5093c4af55c3b7232de0f2593d82c30b8dabd0784455037f25f6bb66a6d0d8f72bc7be0dee2d0a8af44bb4e143257d873268d331722c3253ea5c004e72daf04c875e2054f2b4b2bca2979fd046a1e835600045edf2f159d851a540a91a1ab8fbcb64594d21942bbaa2160535d32496ba7ce4a76c6bdeb9bb4c5cab7bed1ae26564058d0be125803d7019b83b3953c4b0cc1f8299c4edcf6a5faa4765092412d368b277689900e71fb5d47581057adaa2dd494e0f66dc1aa16f3741973b0d9ffa1728aeafab84b777394a7afae0f8eabaa6b740f1c60ca26469f0c9356ec880ad6f4dc01b99bd14d7a4bb8afc97662a9e68b0155e4cdf3caa3402819ac6ce562c8fe06edb50a31cfd7a', - 'ad': '', + encPrivateKey: { + iv: '3a1f2bdb8acbf15f469d57a2', + t: '904d6b1bc0ece8e5df6fefb9efefda7c', + d: '2a7b0c4beb773fa3e3c2158f0bfa654a88c4041184c3b1e01b4ddd2da2c647244a0d66d258b6abb6a9385251bf5d79e6b03ef35bdfafcb400547f8f88adb8bceb7020f2d873d5a74fb5fc561e7bd67cea0a37c49107bf5c96631374dc44ddb1e4a8b5688dc6560fc6143294ed92c3ad8e1696395dfdf15975aa67b9212366dbfcb31191e4f4fe3559c89a92fb1f0f1cc6cbf90d8a062307fce6e7701f6f5169d9247c56dae79b55fba1e10fde562b971ca708c9a4d87e6e9d9e890b88fa0480360420e610c4e41459570e52ae72f349eadf84fc0a68153722de3280becf8a1762e7faebe964f0ad706991c521feda3440d3e1b22f2c221a80490359879bd47c0d059ace81213c74a1e192dbebd8a80cf58c9eb1fe461a971b88d3899baf4c4ef7141623c93fb4a54758f5e1cf9ee35cd00777fa89b24e4ded57219e770de2670619c6e971935c61ae72e3276cf8db49dfa0e91c68222f02d7e0c69b399af505de7e5a90852d83e0a30934b0362db986f3aaefaaf1a96fef3e8165287a3a7f0ee1e072d9dee3aefb86194e1d877d6b34529d45a70ec4573c35a7fe27833c77c3154b0ad02187e4fcecd408bcf4b29a85a5dc358cb479140f4983fcd936141f581764669651530af97d2b7d9416aea7de67e787f3e29ae3eba6672bcd934dc1e308783aa63a4ab46d48d213cf53ad6bd8828011f5bfa3aa5ee24551c694e829b54c93b1dda6c3ddda04756d68a28bec8d044c8af4147680dc5b972d0ca74299b0ab6306b9e7b99bf0557558df120455a272145b7aa792654730f3d670b76d72408f5ce1cf5fbd453d2903fa72cf26397437854ba8abbb731a8107f6a86a01fa98edc81bb42a4c1330f779e7a0fbd1820eaed78e03e40a996e03884b707556be06fd14ee8f4035469210d1d2bb8f58285fc2ab6de3d3cc0e4e1f40c6d9d24b50dc8e2e2374a0aff52031b3736c2982133bb19dd551ce1f953f4ba02b0cf53382c15752e202c138cb42b2322df103ff17fd886dfd5f992b711673cdf16048c4bff19038138b161c2e1783b85fc7b965a91ac4795fcbfebf827940cacdeae57946863aee027df43b36612f3cb8f34dc44396e87c564bf10f5b1a9dfbd6da3d7f4f65024b0b4f8ce51d01c230840941fc4523b17eb1c2522032f410e8328239a11a15ab755c32945ce52966d5bfb4666909ed2ca04d536e4bf92091563dd44d46cbb35e53c2481400058ab3b52a0280d262551073f61db125ee280e2cc1ec0bdf9c4817824261465011e34c2296411384f7f5e16742157c5520f137631edf498aa39c7c32b107e3634cbeb70feea19a233c8bd939d665135c9f7c1bb33cb47edc58bdbbcde9b0b9eb73a46642e4639289a62638fb7813e1eeaadd105c803de8357236f33c4bcf31a876b5867591af8f165eba0b35cf0b0886af17dab35a6a39f8f576387d6ffb9e677ee46fc0f11ff069a2a068fce441ff8f4125095fad228c2bf45c788d641941ed13c0a16fffcafd7c7eff11bb7550c0b7d54eebdbd2066e3bbdb47aaee2b5f1e499726324a40015458c7de1db0abe872594d8e6802deff7ea9518bdb3a3e46f07139267fd67dc570ba8ab04c2b37ce6a34ec73b802c7052a2eef0cae1b0979322ef86395535db80cf2a9a88aa7c2e5cc28a93612a8dafe1982f741d7cec28a866f6c09dba5b99ead24c3df0ca03c6c5afae41f3d39608a8f49b0d6a0b541a159409791c25ede103eb4f79cfbd0cc9c9aa6b591755c1e9fd07b5b9e38ed85b5939e65d127256f6a4c078f8c9d655c4f072f9cbcfb2e1e17eaa83dc62aaab2a6dc3735ee76ce7a215740f795f1fbe7136c7734ae3714438015e8fc383d63775a8abddb23cbc5f906c046bb0b5b31d492a7c151b40ea82c7c966e25820641c55b343b89d6378f90de5983fa76547e9d6c634effdf019a0fd9b6d3e488a5aa94f0710d517ba4f7c1ed82f9f3072612e953e036c0ec7f3c618368362f6da6f3af76056a66aef914805cc8b628f1c11695f760b535ded9ff66727273ae7e12d67a01243d75f22fec8ed1b043122a211c923aa92ecbbe01dd0d7195c3c0e09a2a6ab3eca354963122d5a0ec16e2b2b81b0ddce6ec0a312c492a96a4fd392f1deb6a1f3318541a3f87e5c9e73ee7edd3b855910f412789e25038108e1eaae04dcfb02b4d958c00c630dc8caa87a40798ce7156d2ade882e68832d39fe8f9bce6a995249a7383013a5093c4af55c3b7232de0f2593d82c30b8dabd0784455037f25f6bb66a6d0d8f72bc7be0dee2d0a8af44bb4e143257d873268d331722c3253ea5c004e72daf04c875e2054f2b4b2bca2979fd046a1e835600045edf2f159d851a540a91a1ab8fbcb64594d21942bbaa2160535d32496ba7ce4a76c6bdeb9bb4c5cab7bed1ae26564058d0be125803d7019b83b3953c4b0cc1f8299c4edcf6a5faa4765092412d368b277689900e71fb5d47581057adaa2dd494e0f66dc1aa16f3741973b0d9ffa1728aeafab84b777394a7afae0f8eabaa6b740f1c60ca26469f0c9356ec880ad6f4dc01b99bd14d7a4bb8afc97662a9e68b0155e4cdf3caa3402819ac6ce562c8fe06edb50a31cfd7a', + ad: '', }, - 'symmetricKey': { - 'alg': 'A256GCM', - 'ext': true, - 'k': 'w62OJNWF4G8iWA8ZrTpModiY8dICyHI7ko1vMLb877g=', - 'key_ops': ['encrypt', 'decrypt'], - 'kty': 'oct', + symmetricKey: { + alg: 'A256GCM', + ext: true, + k: 'w62OJNWF4G8iWA8ZrTpModiY8dICyHI7ko1vMLb877g=', + key_ops: ['encrypt', 'decrypt'], + kty: 'oct', }, - 'email': 'insomnia-user@konghq.com', - 'accountId': 'acct_64a477e6b59d43a5a607f84b4f73e3ce', - 'firstName': 'Rick', - 'lastName': 'Morty', + email: 'insomnia-user@konghq.com', + accountId: 'acct_64a477e6b59d43a5a607f84b4f73e3ce', + firstName: 'Rick', + lastName: 'Morty', }; const userVerifyA = { - 'sessionStarterId': 'strt_157355638c2c41d19f2ebc366b2a18e6', - 'srpB': '4e6480c057c206e68ddf3d2e6dba06f1a27d528af2b15c01058da1d37f0d4bfe70cfb0147d8c2092fb9d276f46a114ea3ad9ef2564c2c4ca39e2b82387b0c9ca236717a719dd793feda4392bf7c928ff0728f8a8abe89d6be29d8dc7ab285e12ab4e04e7cb309ddf585c97eb15e7181a96f4ecb73bd0cf3c476a2f9869e3f907107a6419bbc76990a761f1e7b073dffe9c295e06324b41740bde2627221f135c3ef3cb49851d30370c5d9d96d47decc849740a9bc4e1c2c1169dcbd2bd1213a5f87310332c343c1340644d172e01f2e21c71184d69c48faf6eb7f4cac5f56a68747b202314f7f05dc24c728f2bef0d845d3b7bc1b381d5871c8fda153334e353', + sessionStarterId: 'strt_157355638c2c41d19f2ebc366b2a18e6', + srpB: '4e6480c057c206e68ddf3d2e6dba06f1a27d528af2b15c01058da1d37f0d4bfe70cfb0147d8c2092fb9d276f46a114ea3ad9ef2564c2c4ca39e2b82387b0c9ca236717a719dd793feda4392bf7c928ff0728f8a8abe89d6be29d8dc7ab285e12ab4e04e7cb309ddf585c97eb15e7181a96f4ecb73bd0cf3c476a2f9869e3f907107a6419bbc76990a761f1e7b073dffe9c295e06324b41740bde2627221f135c3ef3cb49851d30370c5d9d96d47decc849740a9bc4e1c2c1169dcbd2bd1213a5f87310332c343c1340644d172e01f2e21c71184d69c48faf6eb7f4cac5f56a68747b202314f7f05dc24c728f2bef0d845d3b7bc1b381d5871c8fda153334e353', }; const userVerifyM1 = { - 'srpM2': 'f0f748c3bdc4dc3f1404b74cebd10d0c7ce20c6107a0ce7f5888c04219f1a594', + srpM2: 'f0f748c3bdc4dc3f1404b74cebd10d0c7ce20c6107a0ce7f5888c04219f1a594', }; const allRoles = [ { - 'id': 'role_d32b9d6c-1fb2-4ac1-b780-b4c15074d6cb', - 'name': 'owner', - 'description': 'Owner can manage the organization and also delete it.', + id: 'role_d32b9d6c-1fb2-4ac1-b780-b4c15074d6cb', + name: 'owner', + description: 'Owner can manage the organization and also delete it.', }, { - 'id': 'role_013aeb67-15c9-42c5-bcd0-5c70a33f8719', - 'name': 'admin', - 'description': 'Admin can only manage the organization.', + id: 'role_013aeb67-15c9-42c5-bcd0-5c70a33f8719', + name: 'admin', + description: 'Admin can only manage the organization.', }, { - 'id': 'role_3fbb17e4-249c-47d5-a5ee-b7f1f43a9c63', - 'name': 'member', - 'description': 'Member cannot manage the organization.', + id: 'role_3fbb17e4-249c-47d5-a5ee-b7f1f43a9c63', + name: 'member', + description: 'Member cannot manage the organization.', }, ]; @@ -163,105 +165,101 @@ const userPermissions = { }; const orgInfo = { - 'id': 'org_3d314c35-b9ca-4aec-b57d-04cea38da05c', - 'name': 'Sync', - 'display_name': 'Sync', - 'branding': { - 'logo_url': 'https://d2evto68nv31gd.cloudfront.net/org_98e187f8-a753-4abf-b0b2-58cdb852eba6', + id: 'org_3d314c35-b9ca-4aec-b57d-04cea38da05c', + name: 'Sync', + display_name: 'Sync', + branding: { + logo_url: 'https://d2evto68nv31gd.cloudfront.net/org_98e187f8-a753-4abf-b0b2-58cdb852eba6', }, - 'metadata': { - 'organizationType': 'team', - 'ownerAccountId': 'acct_e9cf786dc67b4dbc8c002359b3cc3d70', + metadata: { + organizationType: 'team', + ownerAccountId: 'acct_e9cf786dc67b4dbc8c002359b3cc3d70', }, }; const currentRole = { - 'roleId': 'role_d32b9d6c-1fb2-4ac1-b780-b4c15074d6cb', - 'name': 'owner', - 'description': 'Owner can manage the organization and also delete it.', + roleId: 'role_d32b9d6c-1fb2-4ac1-b780-b4c15074d6cb', + name: 'owner', + description: 'Owner can manage the organization and also delete it.', }; const storageRule = { - 'enableCloudSync': true, - 'enableGitSync': true, - 'enableLocalVault': true, - 'isOverridden': false + enableCloudSync: true, + enableGitSync: true, + enableLocalVault: true, + isOverridden: false, }; const members = { - 'start': 0, - 'limit': 0, - 'length': 0, - 'total': 2, - 'next': '', - 'members': [ + start: 0, + limit: 0, + length: 0, + total: 2, + next: '', + members: [ { - 'user_id': 'acct_e9cf786dc67b4dbc8c002359b3cc3d70', - 'picture': 'https://s.gravatar.com/avatar/5301bf735ebace330bb801abb593dc78?s=480\u0026r=pg\u0026d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fwe.png', - 'name': 'wei.yao+2@konghq.com ', - 'email': 'wei.yao+2@konghq.com', - 'role_name': 'owner', - 'created': '2024-08-28T07:02:04.341983Z', + user_id: 'acct_e9cf786dc67b4dbc8c002359b3cc3d70', + picture: + 'https://s.gravatar.com/avatar/5301bf735ebace330bb801abb593dc78?s=480\u0026r=pg\u0026d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fwe.png', + name: 'wei.yao+2@konghq.com ', + email: 'wei.yao+2@konghq.com', + role_name: 'owner', + created: '2024-08-28T07:02:04.341983Z', }, { - 'user_id': 'acct_f883f98dbb9945fba7bb23925361e02a', - 'picture': 'https://s.gravatar.com/avatar/fe822a9c78b8154da82635055895e6e6?s=480\u0026r=pg\u0026d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fwe.png', - 'name': 'wei.yao+3@konghq.com ', - 'email': 'wei.yao+3@konghq.com', - 'role_name': 'member', - 'created': '2024-09-12T11:40:43.168144Z', + user_id: 'acct_f883f98dbb9945fba7bb23925361e02a', + picture: + 'https://s.gravatar.com/avatar/fe822a9c78b8154da82635055895e6e6?s=480\u0026r=pg\u0026d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fwe.png', + name: 'wei.yao+3@konghq.com ', + email: 'wei.yao+3@konghq.com', + role_name: 'member', + created: '2024-09-12T11:40:43.168144Z', }, ], }; const invites = { - 'start': 0, - 'limit': 3, - 'length': 0, - 'total': 3, - 'next': '', - 'invitations': [ + start: 0, + limit: 3, + length: 0, + total: 3, + next: '', + invitations: [ { - 'id': 'uinv_1dmvK1rTehbiBV85', - 'inviter': { - 'name': 'wei.yao+2@konghq.com ', + id: 'uinv_1dmvK1rTehbiBV85', + inviter: { + name: 'wei.yao+2@konghq.com ', }, - 'invitee': { - 'email': 'wei.yao@konghq.com', + invitee: { + email: 'wei.yao@konghq.com', }, - 'created_at': '2024-09-14T10:16:10.513Z', - 'expires_at': '2024-09-21T10:16:10.513Z', - 'roles': [ - 'member', - ], + created_at: '2024-09-14T10:16:10.513Z', + expires_at: '2024-09-21T10:16:10.513Z', + roles: ['member'], }, { - 'id': 'uinv_T9uaMMeoRQQVKF2E', - 'inviter': { - 'name': 'wei.yao+2@konghq.com ', + id: 'uinv_T9uaMMeoRQQVKF2E', + inviter: { + name: 'wei.yao+2@konghq.com ', }, - 'invitee': { - 'email': 'wei.yao+6@konghq.com', + invitee: { + email: 'wei.yao+6@konghq.com', }, - 'created_at': '2024-09-12T10:33:45.320Z', - 'expires_at': '2024-09-19T10:33:45.320Z', - 'roles': [ - 'member', - ], + created_at: '2024-09-12T10:33:45.320Z', + expires_at: '2024-09-19T10:33:45.320Z', + roles: ['member'], }, { - 'id': 'uinv_TIYVQQC2aH7Ev5hW', - 'inviter': { - 'name': 'wei.yao+2@konghq.com ', + id: 'uinv_TIYVQQC2aH7Ev5hW', + inviter: { + name: 'wei.yao+2@konghq.com ', }, - 'invitee': { - 'email': 'wei.yao+4@konghq.com', + invitee: { + email: 'wei.yao+4@konghq.com', }, - 'created_at': '2024-09-12T10:03:51.638Z', - 'expires_at': '2024-09-19T10:03:51.638Z', - 'roles': [ - 'member', - ], + created_at: '2024-09-12T10:03:51.638Z', + expires_at: '2024-09-19T10:03:51.638Z', + roles: ['member'], }, ], }; @@ -271,19 +269,15 @@ interface CollaboratorSearchResultItem { picture: string; type: CollaboratorType; name: string; -}; +} interface EmailsList { invitesCount: number; membersCount: number; groupsCount: number; -}; +} -const getEmailsForInviteSearch = ({ - invitesCount = 0, - membersCount = 0, - groupsCount = 0, -}: EmailsList) => { +const getEmailsForInviteSearch = ({ invitesCount = 0, membersCount = 0, groupsCount = 0 }: EmailsList) => { const emails: CollaboratorSearchResultItem[] = []; for (let i = 0; i < groupsCount; i++) { @@ -332,11 +326,7 @@ const OWNER_ROLE_ID = 'role_b3cf4fed-9208-497a-93c6-ae1a82b7b889'; const ADMIN_ROLE_ID = 'role_1c7938bc-c53b-49a1-819e-72f0c3a5baa6'; const MEMBER_ROLE_ID = 'role_4c924f55-7706-4de8-94ab-0a2085890641'; -const getCollaborators = ({ - invitesCount = 0, - membersCount = 0, - groupsCount = 0, -}: EmailsList) => { +const getCollaborators = ({ invitesCount = 0, membersCount = 0, groupsCount = 0 }: EmailsList) => { const collaborators: Collaborator[] = []; for (let i = 0; i < groupsCount; i++) { @@ -507,10 +497,7 @@ export default (app: Application) => { name: _req.body.name, }; - const projects = [ - ...(projectsByOrgId.get(personalOrg.id) || []), - newProject, - ]; + const projects = [...(projectsByOrgId.get(personalOrg.id) || []), newProject]; projectsByOrgId.set(personalOrg.id, projects); res.status(200).send({ ...newProject, organizationId: personalOrg.id }); @@ -529,17 +516,14 @@ export default (app: Application) => { name: _req.body.name, }; - const projects = [ - ...(projectsByOrgId.get(organization.id) || []), - newProject, - ]; + const projects = [...(projectsByOrgId.get(organization.id) || []), newProject]; projectsByOrgId.set(organization.id, projects); res.status(200).send({ ...newProject, organizationId: organization.id }); }); app.post('/v1/organizations/:organizationId/collaborators', (_req, res) => { - res.json({ 'data': [] }); + res.json({ data: [] }); }); app.get('/v1/organizations/roles', (_req, res) => { @@ -576,36 +560,41 @@ export default (app: Application) => { app.post('/v1/desktop/organizations/:organizationId/collaborators/start-adding', (_req, res) => { res.json({ - 'acct_2346c8e88dae47e2a1a5cae04dc68ea3': { - 'accountId': 'acct_2346c8e88dae47e2a1a5cae04dc68ea3', - 'publicKey': '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"o7QI0X9cue5ErinBTTz24YuTXGCbQQfhuqXKEq8xpBinqL8lW0CgTe3HqDDyGN6Ip3kE2wCCBLNTTheSS3FB0172VhsqE2mnlBsopfGWbNmFT-cT517464u9yrsFK2ywVDURDDjdh2BSl1T-3axy1P74BjvcOz7nzlAMNfT8Wp41Dwzb5o9-HPU_1nJQYzOb1zJlV1pwKzeufq81tNecT7td1QB3mnXhJAFFbRINiGu-uIaP7gl-J4ICOTh0Tjzzn7fKC-3EUbfLRvFUZBtRcZncWa5OjuGB5DhgHj8mcWvGyP_3gKzvOB2b4piE6N3NnbwO9-skIw5MdY-kQMvJLQ=="}', - 'autoLinked': false, + acct_2346c8e88dae47e2a1a5cae04dc68ea3: { + accountId: 'acct_2346c8e88dae47e2a1a5cae04dc68ea3', + publicKey: + '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"o7QI0X9cue5ErinBTTz24YuTXGCbQQfhuqXKEq8xpBinqL8lW0CgTe3HqDDyGN6Ip3kE2wCCBLNTTheSS3FB0172VhsqE2mnlBsopfGWbNmFT-cT517464u9yrsFK2ywVDURDDjdh2BSl1T-3axy1P74BjvcOz7nzlAMNfT8Wp41Dwzb5o9-HPU_1nJQYzOb1zJlV1pwKzeufq81tNecT7td1QB3mnXhJAFFbRINiGu-uIaP7gl-J4ICOTh0Tjzzn7fKC-3EUbfLRvFUZBtRcZncWa5OjuGB5DhgHj8mcWvGyP_3gKzvOB2b4piE6N3NnbwO9-skIw5MdY-kQMvJLQ=="}', + autoLinked: false, }, - 'acct_2a1f5086018442b98fbb15120b75a27e': { - 'accountId': 'acct_2a1f5086018442b98fbb15120b75a27e', - 'publicKey': '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"nvmA4jWOAUiopX7Ct9Z5mH6mmTB7I4SlSgDNCMtVxHKjEEegXuxTqkScklHnrZCT7ohmWY-6ouJW4ocjln3Falu8lxxB0V7YqBrxgf81lKlDIGr5f0VYp-R9JSBtR6btVj3xV-3I3APGH5lRBW0VGTdgrBaRAl7o9_4hy7xLSy_hqgqdH2-CS2gEZfRjN-1kjSI4nvqD1BSMfyWhu-pbhP6WdhmOa3JkWLPRtxQInv14Kp1-gWjsAfXYOEvldTH4DvCGYkvEBYvSr9FQ6NQKJFOHho4NAyXJhagvuqwc134XuwiFDgCmK0bh1jXR2fy-OR255S0NseArZPkY3l2Tjw=="}', - 'autoLinked': false, + acct_2a1f5086018442b98fbb15120b75a27e: { + accountId: 'acct_2a1f5086018442b98fbb15120b75a27e', + publicKey: + '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"nvmA4jWOAUiopX7Ct9Z5mH6mmTB7I4SlSgDNCMtVxHKjEEegXuxTqkScklHnrZCT7ohmWY-6ouJW4ocjln3Falu8lxxB0V7YqBrxgf81lKlDIGr5f0VYp-R9JSBtR6btVj3xV-3I3APGH5lRBW0VGTdgrBaRAl7o9_4hy7xLSy_hqgqdH2-CS2gEZfRjN-1kjSI4nvqD1BSMfyWhu-pbhP6WdhmOa3JkWLPRtxQInv14Kp1-gWjsAfXYOEvldTH4DvCGYkvEBYvSr9FQ6NQKJFOHho4NAyXJhagvuqwc134XuwiFDgCmK0bh1jXR2fy-OR255S0NseArZPkY3l2Tjw=="}', + autoLinked: false, }, - 'acct_6694e55cce2c4dacb69c86844ba92d91': { - 'accountId': 'acct_6694e55cce2c4dacb69c86844ba92d91', - 'publicKey': '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"wCd42bJqAZz5lRMk8MdMoF35ga9yhIjirMUhUXXKvA29LUYGsT6J_LxF6pXWV7CSZdxZPrf8Ur8L2AC7gz0ESHfV-uAVPBFnPrGBTiiHTBCDAtkt8tW3hqullJxfLS8PsGL6IYGYloq9gbKXiz-u37ba282vYQbbzkWO_382QJKS6eYAlE5JOpxmtNl7r5a3Okxz8JekBN5WhZrxEQzOv7ov7zmmRZPBgCm3Xo7RzAuUpBam1EkO5UvGL3DEjnc_Kx7R9jVbmLgDcryJDooKiCVLWv-tyg9H5QYMVd76uxAcQE9fJNoxSX-UU-Tu78-6CHk68IyTa2Rf4BwvSZJw-Q=="}', - 'autoLinked': false, + acct_6694e55cce2c4dacb69c86844ba92d91: { + accountId: 'acct_6694e55cce2c4dacb69c86844ba92d91', + publicKey: + '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"wCd42bJqAZz5lRMk8MdMoF35ga9yhIjirMUhUXXKvA29LUYGsT6J_LxF6pXWV7CSZdxZPrf8Ur8L2AC7gz0ESHfV-uAVPBFnPrGBTiiHTBCDAtkt8tW3hqullJxfLS8PsGL6IYGYloq9gbKXiz-u37ba282vYQbbzkWO_382QJKS6eYAlE5JOpxmtNl7r5a3Okxz8JekBN5WhZrxEQzOv7ov7zmmRZPBgCm3Xo7RzAuUpBam1EkO5UvGL3DEjnc_Kx7R9jVbmLgDcryJDooKiCVLWv-tyg9H5QYMVd76uxAcQE9fJNoxSX-UU-Tu78-6CHk68IyTa2Rf4BwvSZJw-Q=="}', + autoLinked: false, }, - 'acct_72196d3295b243b48ea4de15391873b7': { - 'accountId': 'acct_72196d3295b243b48ea4de15391873b7', - 'publicKey': '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"94S0IWkw5RgnhJy1Dspynt1gsRnOrG_A5UqI2sbp8fNCdlU9Z0M-r9O-ern0Wgupxxqt8s3xpQzaRYSPcCOK4z9F-w2MT6wIKn7EKKWCpXa94pra4J5abVukwtbPILIi9-uKu8RisnaeYT82OfZKAaQi-J24yzRI7qYLyS0GCrSxWgr1-wVzeRrE8gnwQU677TVAyGDTioz3EQ2-pB4fTkXdrBlVZ8qQkruwcTJ--rr550MD1cRK95J0jT1qGn8e0bTMW5lHP3dZH7vveFj1RP3cD7jnO6b3pD7jhDaMLJqXw0Nvxru__lToP-_r054Ea8ffEWVjygtqvplxq4R3Cw=="}', - 'autoLinked': false, + acct_72196d3295b243b48ea4de15391873b7: { + accountId: 'acct_72196d3295b243b48ea4de15391873b7', + publicKey: + '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"94S0IWkw5RgnhJy1Dspynt1gsRnOrG_A5UqI2sbp8fNCdlU9Z0M-r9O-ern0Wgupxxqt8s3xpQzaRYSPcCOK4z9F-w2MT6wIKn7EKKWCpXa94pra4J5abVukwtbPILIi9-uKu8RisnaeYT82OfZKAaQi-J24yzRI7qYLyS0GCrSxWgr1-wVzeRrE8gnwQU677TVAyGDTioz3EQ2-pB4fTkXdrBlVZ8qQkruwcTJ--rr550MD1cRK95J0jT1qGn8e0bTMW5lHP3dZH7vveFj1RP3cD7jnO6b3pD7jhDaMLJqXw0Nvxru__lToP-_r054Ea8ffEWVjygtqvplxq4R3Cw=="}', + autoLinked: false, }, - 'acct_fe023b1398ab48fd8f9d3dfb622f5bf6': { - 'accountId': 'acct_fe023b1398ab48fd8f9d3dfb622f5bf6', - 'publicKey': '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"s0W6IbaPmPaMgzf2-rGOffm4tNg8_ZykiX2C6ZgFdC-GsMGiF08pSjD7UfGTPSTIWFv4Ncz6D0J8wbFBa87IYTuIZhewbNAqRcX1eu_g0-4dNIw9KqhvIoy_O-r-MT1T11TuU5gWWyHw8mY2Aax9Z_JDdDMQc-dP_FqxGCTIHfe52xQNaCL3AgMp0nU5sDUp_vo3YXSWk0yuERqQ9TMcB9l27hQhbHZHDfsdHTodXutbBG5MwpcDBppriBVlMVjY8M7QHt61C7KF5mhgniEd2msF0bAZZaVz1ibZ9QNdFHHPrdfLLQvPyZFD4m8a7Wt0Qcq9FfrFubWv1208Ocet3Q=="}', - 'autoLinked': false, + acct_fe023b1398ab48fd8f9d3dfb622f5bf6: { + accountId: 'acct_fe023b1398ab48fd8f9d3dfb622f5bf6', + publicKey: + '{"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"s0W6IbaPmPaMgzf2-rGOffm4tNg8_ZykiX2C6ZgFdC-GsMGiF08pSjD7UfGTPSTIWFv4Ncz6D0J8wbFBa87IYTuIZhewbNAqRcX1eu_g0-4dNIw9KqhvIoy_O-r-MT1T11TuU5gWWyHw8mY2Aax9Z_JDdDMQc-dP_FqxGCTIHfe52xQNaCL3AgMp0nU5sDUp_vo3YXSWk0yuERqQ9TMcB9l27hQhbHZHDfsdHTodXutbBG5MwpcDBppriBVlMVjY8M7QHt61C7KF5mhgniEd2msF0bAZZaVz1ibZ9QNdFHHPrdfLLQvPyZFD4m8a7Wt0Qcq9FfrFubWv1208Ocet3Q=="}', + autoLinked: false, }, }); }); app.get('/v1/organizations/:organizationId/my-project-keys', (_req, res) => { - res.json({ 'projectKeys': [], 'members': [] }); + res.json({ projectKeys: [], members: [] }); }); app.post('/v1/organizations/:organizationId/reconcile-keys', (_req, res) => { diff --git a/packages/insomnia-smoke-test/server/oauth.ts b/packages/insomnia-smoke-test/server/oauth.ts index 2daeb692ec..61f2040311 100644 --- a/packages/insomnia-smoke-test/server/oauth.ts +++ b/packages/insomnia-smoke-test/server/oauth.ts @@ -80,26 +80,19 @@ export const oauthRoutes = async (port: number) => { }, ], pkce: { - methods: [ - 'S256', - 'plain', - ], + methods: ['S256', 'plain'], required: (_, client) => { // Require PKCE for the PKCE client id return client.clientId === clientIDAuthorizationCodePKCE; }, }, - responseTypes: [ - 'code', - 'id_token', 'id_token token', - 'none', - ], + responseTypes: ['code', 'id_token', 'id_token token', 'none'], issueRefreshToken: () => { return false; }, // https://github.com/panva/node-oidc-provider/blob/main/recipes/skip_consent.md loadExistingGrant: async ctx => { - const grantId = (ctx.oidc.result?.consent?.grantId) || (ctx.oidc.session!.grantIdFor(ctx.oidc.client!.clientId)); + const grantId = ctx.oidc.result?.consent?.grantId || ctx.oidc.session!.grantIdFor(ctx.oidc.client!.clientId); if (grantId) { const grant = await ctx.oidc.provider.Grant.find(grantId); @@ -138,32 +131,21 @@ export const oauthRoutes = async (port: number) => { oauthRouter.get('/id-token', async (req, res) => { const client = await oidc.Client.find(clientIDImplicit); if (!client) { - res - .status(500) - .header('Content-Type', 'text/plain') - .send('Client not found'); + res.status(500).header('Content-Type', 'text/plain').send('Client not found'); return; } const authorizationHeader = req.header('Authorization'); if (!authorizationHeader) { - res - .status(400) - .header('Content-Type', 'text/plain') - .send('Missing Authorization header'); + res.status(400).header('Content-Type', 'text/plain').send('Missing Authorization header'); return; } try { const validated = await oidc.IdToken.validate(extractToken(authorizationHeader), client); - res - .status(200) - .json(validated); + res.status(200).json(validated); } catch (err) { - res - .status(500) - .header('Content-Type', 'text/plain') - .send('Invalid authorization header'); + res.status(500).header('Content-Type', 'text/plain').send('Invalid authorization header'); } }); @@ -171,32 +153,22 @@ export const oauthRoutes = async (port: number) => { oauthRouter.get('/client-credential', async (req, res) => { const authorizationHeader = req.header('Authorization'); if (!authorizationHeader) { - res - .status(400) - .header('Content-Type', 'text/plain') - .send('Missing Authorization header'); + res.status(400).header('Content-Type', 'text/plain').send('Missing Authorization header'); return; } const clientCredentials = await oidc.ClientCredentials.find(extractToken(authorizationHeader)); if (!clientCredentials) { - res - .status(400) - .header('Content-Type', 'text/plain') - .send('Invalid client credentials'); + res.status(400).header('Content-Type', 'text/plain').send('Invalid client credentials'); return; } - res - .status(200) - .json(clientCredentials); + res.status(200).json(clientCredentials); }); oauthRouter.get('/interaction/:uid', async (req, res, next) => { try { - const { - uid, prompt, - } = await oidc.interactionDetails(req, res); + const { uid, prompt } = await oidc.interactionDetails(req, res); switch (prompt.name) { case 'login': { @@ -227,10 +199,7 @@ export const oauthRoutes = async (port: number) => { try { await oidc.interactionDetails(req, res); - const account = await (oidc.Account as any).findAccount( - null, - req.body.login, - ); + const account = await (oidc.Account as any).findAccount(null, req.body.login); const result = { login: { @@ -267,64 +236,65 @@ function allowLocalhostImplicit(oidc: Provider) { // https://github.com/panva/node-oidc-provider/tree/main/docs#password-grant-type-ropc const parameters = ['username', 'password', 'scope']; function registerROPC(oidc: Provider) { - oidc.registerGrantType('password', async (ctx, next) => { - const params = ctx.oidc.params; + oidc.registerGrantType( + 'password', + async (ctx, next) => { + const params = ctx.oidc.params; - if (!params) { - throw new Error('invalid params provided'); - } + if (!params) { + throw new Error('invalid params provided'); + } - if (!ctx.oidc.client) { - throw new Error('invalid client provided'); - } + if (!ctx.oidc.client) { + throw new Error('invalid client provided'); + } - if (typeof params.username !== 'string' || typeof params.password !== 'string') { - throw new Error('invalid credentials provided'); - } + if (typeof params.username !== 'string' || typeof params.password !== 'string') { + throw new Error('invalid credentials provided'); + } - const account = await ctx.oidc.provider.Account.findAccount( - ctx, - params.username - ); - if (!account) { - throw new Error('invalid account'); - } + const account = await ctx.oidc.provider.Account.findAccount(ctx, params.username); + if (!account) { + throw new Error('invalid account'); + } - const grant = new ctx.oidc.provider.Grant({ - clientId: ctx.oidc.client.clientId, - accountId: account.accountId, - }); - await grant.save(); + const grant = new ctx.oidc.provider.Grant({ + clientId: ctx.oidc.client.clientId, + accountId: account.accountId, + }); + await grant.save(); - const { AccessToken } = ctx.oidc.provider; - const at = new AccessToken({ - accountId: account.accountId, - client: ctx.oidc.client, - grantId: grant.jti, - gty: 'password', - scope: typeof params.scope === 'string' ? params.scope : '', - claims: { - userinfo: { - sub: { - value: account.accountId, + const { AccessToken } = ctx.oidc.provider; + const at = new AccessToken({ + accountId: account.accountId, + client: ctx.oidc.client, + grantId: grant.jti, + gty: 'password', + scope: typeof params.scope === 'string' ? params.scope : '', + claims: { + userinfo: { + sub: { + value: account.accountId, + }, }, }, - }, - }); + }); - const accessToken = await at.save(); + const accessToken = await at.save(); - /* eslint-disable camelcase */ - ctx.body = { - access_token: accessToken, - expires_in: at.expiration, - scope: at.scope, - token_type: at.tokenType, - }; - /* eslint-enable camelcase */ + /* eslint-disable camelcase */ + ctx.body = { + access_token: accessToken, + expires_in: at.expiration, + scope: at.scope, + token_type: at.tokenType, + }; + /* eslint-enable camelcase */ - await next(); - }, parameters); + await next(); + }, + parameters, + ); } function extractToken(authorizationHeader: string) { diff --git a/packages/insomnia-smoke-test/server/websocket.ts b/packages/insomnia-smoke-test/server/websocket.ts index 29344ccade..112d3506e4 100644 --- a/packages/insomnia-smoke-test/server/websocket.ts +++ b/packages/insomnia-smoke-test/server/websocket.ts @@ -79,11 +79,11 @@ const upgrade = (wss: WebSocketServer, request: IncomingMessage, socket: Socket, return redirectOnSuccess(socket); } if (request.url === '/delay') { - const delaySec = Number.parseInt(request.headers.duration as string || '5'); - setTimeout(function() { + const delaySec = Number.parseInt((request.headers.duration as string) || '5'); + setTimeout(function () { redirectOnSuccess(socket); }, delaySec * 1000); - return ; + return; } if (request.url === '/basic-auth') { // login with user:password diff --git a/packages/insomnia-smoke-test/tests/critical/backup.test.ts b/packages/insomnia-smoke-test/tests/critical/backup.test.ts index fa0aec3350..67a458acad 100644 --- a/packages/insomnia-smoke-test/tests/critical/backup.test.ts +++ b/packages/insomnia-smoke-test/tests/critical/backup.test.ts @@ -5,25 +5,24 @@ import path from 'path'; import { test } from '../../playwright/test'; test('can backup data on new version available', async ({ app, page }) => { - - const dataPath = await app.evaluate(async ({ app }) => app.getPath('userData')); - let foundBackups = false; - // retry 5 times - for (let i = 0; i < 5; i++) { - console.log('Retry', i); - if (fs.existsSync(path.join(dataPath, 'backups'))) { - console.log('Backups exists!'); - const rootBackupsFolder = fs.readdirSync(path.join(dataPath, 'backups')); - const backupDir = fs.readdirSync(path.join(dataPath, 'backups', rootBackupsFolder[0])); - const hasFilesInsideBackup = backupDir.length > 0; - const hasProjectDbFile = backupDir.includes('insomnia.Project.db'); - foundBackups = hasFilesInsideBackup && hasProjectDbFile; - break; - } else { - console.log('backups not found. Waiting 5 seconds...'); - await page.waitForTimeout(5000); - } + const dataPath = await app.evaluate(async ({ app }) => app.getPath('userData')); + let foundBackups = false; + // retry 5 times + for (let i = 0; i < 5; i++) { + console.log('Retry', i); + if (fs.existsSync(path.join(dataPath, 'backups'))) { + console.log('Backups exists!'); + const rootBackupsFolder = fs.readdirSync(path.join(dataPath, 'backups')); + const backupDir = fs.readdirSync(path.join(dataPath, 'backups', rootBackupsFolder[0])); + const hasFilesInsideBackup = backupDir.length > 0; + const hasProjectDbFile = backupDir.includes('insomnia.Project.db'); + foundBackups = hasFilesInsideBackup && hasProjectDbFile; + break; + } else { + console.log('backups not found. Waiting 5 seconds...'); + await page.waitForTimeout(5000); } + } - await expect(foundBackups).toBe(true); + await expect(foundBackups).toBe(true); }); diff --git a/packages/insomnia-smoke-test/tests/critical/scratchpad.test.ts b/packages/insomnia-smoke-test/tests/critical/scratchpad.test.ts index e413166196..1b041fb183 100644 --- a/packages/insomnia-smoke-test/tests/critical/scratchpad.test.ts +++ b/packages/insomnia-smoke-test/tests/critical/scratchpad.test.ts @@ -1,8 +1,8 @@ import { test } from '../../playwright/test'; test('can open scratchpad', async ({ page }) => { - await page.locator('[data-testid="user-dropdown"]').click(); - await page.getByText('Log Out').click(); - await page.getByLabel('Use the Scratch Pad').click(); - await page.getByText('Welcome to the Scratch Pad').click(); + await page.locator('[data-testid="user-dropdown"]').click(); + await page.getByText('Log Out').click(); + await page.getByLabel('Use the Scratch Pad').click(); + await page.getByText('Welcome to the Scratch Pad').click(); }); diff --git a/packages/insomnia-smoke-test/tests/migration/local-to-cloud-projects.test.ts b/packages/insomnia-smoke-test/tests/migration/local-to-cloud-projects.test.ts index 66156cab0a..9b46fe572f 100644 --- a/packages/insomnia-smoke-test/tests/migration/local-to-cloud-projects.test.ts +++ b/packages/insomnia-smoke-test/tests/migration/local-to-cloud-projects.test.ts @@ -20,9 +20,7 @@ testWithLegacyDatabase('Run data migration to version 8', async ({ page, userCon // Migration takes a while, adding this to avoid test timeout before it ends test.slow(); - await page.getByLabel('Continue with Google').click(), - - await page.locator('input[name="code"]').click(); + await page.getByLabel('Continue with Google').click(), await page.locator('input[name="code"]').click(); await page.locator('input[name="code"]').fill(userConfig.code); await page.getByRole('button', { name: 'Log in' }).click(); diff --git a/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts b/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts index 85eae3d7a3..a385aae568 100644 --- a/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/after-response-script-features.test.ts @@ -1,83 +1,89 @@ import { expect } from '@playwright/test'; import { loadFixture } from '../../playwright/paths'; -import { test } from '../../playwright/test';; +import { test } from '../../playwright/test'; test.describe('after-response script features tests', async () => { - test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); - test.beforeEach(async ({ app, page }) => { - const text = await loadFixture('after-response-collection.yaml'); - await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + test.beforeEach(async ({ app, page }) => { + const text = await loadFixture('after-response-collection.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); - await page.getByLabel('Import').click(); - await page.locator('[data-test-id="import-from-clipboard"]').click(); - await page.getByRole('button', { name: 'Scan' }).click(); - await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); - await page.getByLabel('After-response Scripts').click(); + await page.getByLabel('After-response Scripts').click(); + }); + + test('post: insomnia.test and insomnia.expect can work together', async ({ page }) => { + await page.getByLabel('Request Collection').getByTestId('tests with expect and test').press('Enter'); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + + // verify + await page.getByRole('tab', { name: 'Tests' }).click(); + + const responsePane = page.getByTestId('response-pane'); + await expect(responsePane).toContainText('PASS'); + await expect(responsePane).toContainText( + 'FAILunhappy tests | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200', + ); + await expect(responsePane).toContainText('PASShappyTestInFunc'); + await expect(responsePane).toContainText( + 'FAILsadTestInFunc | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200', + ); + await expect(responsePane).toContainText('PASSasyncHappyTestInFunc'); + await expect(responsePane).toContainText( + 'FAILasyncSadTestInFunc | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200', + ); + }); + + test('environment and baseEnvironment can be persisted', async ({ page }) => { + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + await page.getByLabel('Request Collection').getByTestId('persist environments').press('Enter'); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + + // verify response + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + await expect(statusTag).toContainText('200 OK'); + + // verify persisted environment + await page.getByRole('button', { name: 'Manage Environments' }).click(); + await page.getByRole('button', { name: 'Manage collection environments' }).click(); + const responseBody = page.getByRole('dialog').getByTestId('CodeEditor').locator('.CodeMirror-line'); + const rows = await responseBody.allInnerTexts(); + const bodyJson = JSON.parse(rows.join(' ')); + + expect(bodyJson).toEqual({ + // no environment is selected so the environment value will be persisted to the base environment + __fromAfterScript1: 'baseEnvironment', + __fromAfterScript2: 'collection', + __fromAfterScript: 'environment', + base_url: 'http://localhost:4010', }); + }); - test('post: insomnia.test and insomnia.expect can work together', async ({ page }) => { - await page.getByLabel('Request Collection').getByTestId('tests with expect and test').press('Enter'); + test('set transient var', async ({ page }) => { + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + await page.getByLabel('Request Collection').getByTestId('transient var').press('Enter'); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - // verify - await page.getByRole('tab', { name: 'Tests' }).click(); + // verify response + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + await expect(statusTag).toContainText('200 OK'); - const responsePane = page.getByTestId('response-pane'); - await expect(responsePane).toContainText('PASS'); - await expect(responsePane).toContainText('FAILunhappy tests | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200'); - await expect(responsePane).toContainText('PASShappyTestInFunc'); - await expect(responsePane).toContainText('FAILsadTestInFunc | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200'); - await expect(responsePane).toContainText('PASSasyncHappyTestInFunc'); - await expect(responsePane).toContainText('FAILasyncSadTestInFunc | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200'); - }); + // verify + await page.getByRole('tab', { name: 'Tests' }).click(); - test('environment and baseEnvironment can be persisted', async ({ page }) => { - const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); - await page.getByLabel('Request Collection').getByTestId('persist environments').press('Enter'); - - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - - // verify response - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - await expect(statusTag).toContainText('200 OK'); - - // verify persisted environment - await page.getByRole('button', { name: 'Manage Environments' }).click(); - await page.getByRole('button', { name: 'Manage collection environments' }).click(); - const responseBody = page.getByRole('dialog').getByTestId('CodeEditor').locator('.CodeMirror-line'); - const rows = await responseBody.allInnerTexts(); - const bodyJson = JSON.parse(rows.join(' ')); - - expect(bodyJson).toEqual({ - // no environment is selected so the environment value will be persisted to the base environment - '__fromAfterScript1': 'baseEnvironment', - '__fromAfterScript2': 'collection', - '__fromAfterScript': 'environment', - 'base_url': 'http://localhost:4010', - }); - }); - - test('set transient var', async ({ page }) => { - const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); - await page.getByLabel('Request Collection').getByTestId('transient var').press('Enter'); - - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - - // verify response - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - await expect(statusTag).toContainText('200 OK'); - - // verify - await page.getByRole('tab', { name: 'Tests' }).click(); - - const rows = page.getByTestId('test-result-row'); - await expect(rows.first()).toContainText('PASS'); - }); + const rows = page.getByTestId('test-result-row'); + await expect(rows.first()).toContainText('PASS'); + }); }); diff --git a/packages/insomnia-smoke-test/tests/smoke/analytics.test.ts b/packages/insomnia-smoke-test/tests/smoke/analytics.test.ts index 0640f675d4..d4fcc20702 100644 --- a/packages/insomnia-smoke-test/tests/smoke/analytics.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/analytics.test.ts @@ -3,93 +3,94 @@ import { expect } from '@playwright/test'; import { test } from '../../playwright/test'; interface SegmentRequestData { - batch: { - timestamp: string; - integrations: {}; - type: string; - properties: {}; - name?: string; - context: { - app: { - name: string; - version: string; - }; - os: { - name: string; - version: string; - }; - library: { - name: string; - version: string; - }; - }; - anonymousId: string; - userId: string; - messageId: string; - _metadata: { - nodeVersion: string; - jsRuntime: string; - }; - event?: string; - }[]; - writeKey: string; - sentAt: string; + batch: { + timestamp: string; + integrations: {}; + type: string; + properties: {}; + name?: string; + context: { + app: { + name: string; + version: string; + }; + os: { + name: string; + version: string; + }; + library: { + name: string; + version: string; + }; + }; + anonymousId: string; + userId: string; + messageId: string; + _metadata: { + nodeVersion: string; + jsRuntime: string; + }; + event?: string; + }[]; + writeKey: string; + sentAt: string; } interface SegmentLog { - url: string; - data: SegmentRequestData[]; + url: string; + data: SegmentRequestData[]; } test('analytics events are sent', async ({ page, app }) => { - await app.evaluate(async ({ session }) => { - // Capture segment requests to a global variable in main process - globalThis.segmentLogs = []; + await app.evaluate(async ({ session }) => { + // Capture segment requests to a global variable in main process + globalThis.segmentLogs = []; - session.defaultSession.webRequest.onBeforeRequest((details, callback) => { - if (details.url.includes('segment')) { - globalThis.segmentLogs.push({ url: details.url, data: details.uploadData }); - } - callback({ cancel: false }); - }); + session.defaultSession.webRequest.onBeforeRequest((details, callback) => { + if (details.url.includes('segment')) { + globalThis.segmentLogs.push({ url: details.url, data: details.uploadData }); + } + callback({ cancel: false }); }); + }); - // Create a collection and requests that cause analytics events: - await page.getByRole('button', { name: 'Create document', exact: true }).click(); - await page.getByRole('button', { name: 'Create', exact: true }).click(); + // Create a collection and requests that cause analytics events: + await page.getByRole('button', { name: 'Create document', exact: true }).click(); + await page.getByRole('button', { name: 'Create', exact: true }).click(); - await page.getByTestId('workspace-debug').click(); + await page.getByTestId('workspace-debug').click(); - for (let i = 0; i < 10; i++) { - await page.getByLabel('Create in collection').click(); - await page.getByRole('menuitemradio', { name: 'HTTP Request' }).press('Enter'); - } + for (let i = 0; i < 10; i++) { + await page.getByLabel('Create in collection').click(); + await page.getByRole('menuitemradio', { name: 'HTTP Request' }).press('Enter'); + } - const segmentLogs = await app.evaluate(() => globalThis.segmentLogs); + const segmentLogs = await app.evaluate(() => globalThis.segmentLogs); - const decodedLogs: SegmentLog[] = segmentLogs.map((log: { url: string; data: { type: string; bytes: number[] }[] }) => { - return { - url: log.url, - data: log.data.map(data => JSON.parse(Buffer.from(Object.values(data.bytes)).toString('utf-8'))), - }; - }); + const decodedLogs: SegmentLog[] = segmentLogs.map( + (log: { url: string; data: { type: string; bytes: number[] }[] }) => { + return { + url: log.url, + data: log.data.map(data => JSON.parse(Buffer.from(Object.values(data.bytes)).toString('utf-8'))), + }; + }, + ); - const analyticsBatch = decodedLogs[0].data[0].batch; - const [appStartEvent, ...restEvents] = analyticsBatch; + const analyticsBatch = decodedLogs[0].data[0].batch; + const [appStartEvent, ...restEvents] = analyticsBatch; - // Analytics need at least 15 events to be sent - expect(analyticsBatch.length).toBeGreaterThanOrEqual(5); + // Analytics need at least 15 events to be sent + expect(analyticsBatch.length).toBeGreaterThanOrEqual(5); - // App start event - expect(appStartEvent.anonymousId).toBeTruthy(); - expect(appStartEvent.event).toBe('App Started'); + // App start event + expect(appStartEvent.anonymousId).toBeTruthy(); + expect(appStartEvent.event).toBe('App Started'); - // First event should have userId and anonymousId - expect(restEvents[0].anonymousId).toBeTruthy(); - expect(restEvents[0].userId).toBeTruthy(); - - // Last event should have userId and anonymousId - expect(restEvents.at(-1)?.anonymousId).toBeTruthy(); - expect(restEvents.at(-1)?.userId).toBeTruthy(); + // First event should have userId and anonymousId + expect(restEvents[0].anonymousId).toBeTruthy(); + expect(restEvents[0].userId).toBeTruthy(); + // Last event should have userId and anonymousId + expect(restEvents.at(-1)?.anonymousId).toBeTruthy(); + expect(restEvents.at(-1)?.userId).toBeTruthy(); }); diff --git a/packages/insomnia-smoke-test/tests/smoke/app.test.ts b/packages/insomnia-smoke-test/tests/smoke/app.test.ts index 01b9e0ac2b..d4e44e8aeb 100644 --- a/packages/insomnia-smoke-test/tests/smoke/app.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/app.test.ts @@ -41,27 +41,39 @@ test('can send requests', async ({ app, page }) => { await page.getByRole('menuitem', { name: 'Raw Data' }).click(); await expect(responseBody).toContainText('{"id":"1"}'); - await page.getByLabel('Request Collection').getByTestId('connects to event stream and shows ping response').press('Enter'); + await page + .getByLabel('Request Collection') + .getByTestId('connects to event stream and shows ping response') + .press('Enter'); await page.getByTestId('request-pane').getByRole('button', { name: 'Connect' }).click(); await expect(statusTag).toContainText('200 OK'); await page.getByRole('tab', { name: 'Console' }).click(); await expect(responseBody).toContainText('Connected to 127.0.0.1'); await page.getByTestId('request-pane').getByRole('button', { name: 'Disconnect' }).click(); - await page.getByLabel('Request Collection').getByTestId('sends dummy.csv request and shows rich response').press('Enter'); + await page + .getByLabel('Request Collection') + .getByTestId('sends dummy.csv request and shows rich response') + .press('Enter'); await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); await expect(statusTag).toContainText('200 OK'); await page.getByRole('button', { name: 'Preview' }).click(); await page.getByRole('menuitem', { name: 'Raw Data' }).click(); await expect(responseBody).toContainText('a,b,c'); - await page.getByLabel('Request Collection').getByTestId('sends dummy.xml request and shows raw response').press('Enter'); + await page + .getByLabel('Request Collection') + .getByTestId('sends dummy.xml request and shows raw response') + .press('Enter'); await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); await expect(statusTag).toContainText('200 OK'); await expect(responseBody).toContainText('xml version="1.0"'); await expect(responseBody).toContainText(''); - await page.getByLabel('Request Collection').getByTestId('sends dummy.pdf request and shows rich response').press('Enter'); + await page + .getByLabel('Request Collection') + .getByTestId('sends dummy.pdf request and shows rich response') + .press('Enter'); await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); await expect(statusTag).toContainText('200 OK'); // TODO(filipe): re-add a check for the preview that is less flaky @@ -73,7 +85,10 @@ test('can send requests', async ({ app, page }) => { await expect(statusTag).toContainText('200 OK'); await expect(responseBody).toContainText('basic auth received'); - await page.getByLabel('Request Collection').getByTestId('sends request with cookie and get cookie in response').press('Enter'); + await page + .getByLabel('Request Collection') + .getByTestId('sends request with cookie and get cookie in response') + .press('Enter'); await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); await expect(statusTag).toContainText('200 OK'); await page.getByRole('tab', { name: 'Console' }).click(); diff --git a/packages/insomnia-smoke-test/tests/smoke/command-palette.test.ts b/packages/insomnia-smoke-test/tests/smoke/command-palette.test.ts index cc03ed58dc..82682e55b2 100644 --- a/packages/insomnia-smoke-test/tests/smoke/command-palette.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/command-palette.test.ts @@ -25,7 +25,10 @@ test('Command palette - can switch between requests and workspaces', async ({ ap await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); await page.getByLabel('Smoke tests').click(); - await page.getByTestId('sends request with cookie and get cookie in response').getByText('GET', { exact: true }).click(); + await page + .getByTestId('sends request with cookie and get cookie in response') + .getByText('GET', { exact: true }) + .click(); await page.getByTestId('OneLineEditor').getByText('http://127.0.0.1:4010/cookies').click(); const requestSwitchKeyboardShortcut = process.platform === 'darwin' ? 'Meta+p' : 'Control+p'; await page.locator('body').press(requestSwitchKeyboardShortcut); @@ -40,5 +43,7 @@ test('Command palette - can switch between requests and workspaces', async ({ ap await page.getByPlaceholder('Search and switch between').press('ArrowUp'); await page.getByPlaceholder('Search and switch between').press('ArrowUp'); await page.getByPlaceholder('Search and switch between').press('Enter'); - await expect(page.getByTestId('workspace-context-dropdown').locator('span')).toContainText('E2E testing specification - swagger 2 1.0.0'); + await expect(page.getByTestId('workspace-context-dropdown').locator('span')).toContainText( + 'E2E testing specification - swagger 2 1.0.0', + ); }); diff --git a/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts index 74d79cc9a5..1159b0225e 100644 --- a/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/cookie-editor-interactions.test.ts @@ -4,7 +4,6 @@ import { loadFixture } from '../../playwright/paths'; import { test } from '../../playwright/test'; test.describe('Cookie editor', async () => { - test.beforeEach(async ({ app, page }) => { const text = await loadFixture('simple.yaml'); await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); @@ -16,7 +15,6 @@ test.describe('Cookie editor', async () => { }); test('create and send a cookie', async ({ page }) => { - // Open cookie editor await page.click('button:has-text("Cookies")'); @@ -34,7 +32,9 @@ test.describe('Cookie editor', async () => { // Try to replace text in Raw view await page.getByRole('tab', { name: 'Raw' }).click(); - await page.locator('text=Raw Cookie String >> input[type="text"]').fill('foo2=bar2; Expires=Tue, 19 Jan 2038 03:14:07 GMT; Domain=localhost; Path=/'); + await page + .locator('text=Raw Cookie String >> input[type="text"]') + .fill('foo2=bar2; Expires=Tue, 19 Jan 2038 03:14:07 GMT; Domain=localhost; Path=/'); await page.locator('text=Done').nth(1).click(); await page.getByTestId('cookie-test-iteration-0').click(); diff --git a/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts index 8b39ac4298..1bbc9896a6 100644 --- a/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/dashboard-interactions.test.ts @@ -21,7 +21,11 @@ test.describe('Dashboard', async () => { // Rename Project await page.getByRole('row', { name: 'My Project' }).first().focus(); - await page.getByRole('row', { name: 'My Project' }).first().getByRole('button', { name: 'Project Actions' }).click(); + await page + .getByRole('row', { name: 'My Project' }) + .first() + .getByRole('button', { name: 'Project Actions' }) + .click(); await page.getByRole('menuitemradio', { name: 'Settings' }).click(); await page.getByPlaceholder('My Project').click(); await page.getByPlaceholder('My Project').fill('My Project123'); @@ -36,7 +40,11 @@ test.describe('Dashboard', async () => { // Delete project await page.getByRole('row', { name: 'My Project' }).first().focus(); - await page.getByRole('row', { name: 'My Project' }).first().getByRole('button', { name: 'Project Actions' }).click(); + await page + .getByRole('row', { name: 'My Project' }) + .first() + .getByRole('button', { name: 'Project Actions' }) + .click(); await page.getByRole('menuitemradio', { name: 'Delete' }).click(); await page.getByRole('button', { name: 'Delete' }).click(); @@ -48,7 +56,8 @@ test.describe('Dashboard', async () => { await expect(page.locator('.app')).not.toContainText('Setup Git Sync'); }); }); - test.describe('Interactions', async () => { // Not sure about the name here + test.describe('Interactions', async () => { + // Not sure about the name here test('Can create, rename and delete a document', async ({ page }) => { await page.getByLabel('All Files (0)').click(); diff --git a/packages/insomnia-smoke-test/tests/smoke/design-document-naming.test.ts b/packages/insomnia-smoke-test/tests/smoke/design-document-naming.test.ts index a03288402f..b1ba904a0d 100644 --- a/packages/insomnia-smoke-test/tests/smoke/design-document-naming.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/design-document-naming.test.ts @@ -1,23 +1,23 @@ import { test } from '../../playwright/test'; test.describe('design document operations', async () => { - test('can name design documents', async ({ page }) => { - await page.getByRole('button', { name: 'Create document' }).click(); - await page.locator('body').click(); - await page.getByPlaceholder('Enter a name for your Design Document').fill('jurassic park'); - await page.getByPlaceholder('Enter a name for your Design Document').press('Enter'); - await page.getByTestId('project').click(); - await page.getByLabel('Files').getByLabel('jurassic park').click(); - }); + test('can name design documents', async ({ page }) => { + await page.getByRole('button', { name: 'Create document' }).click(); + await page.locator('body').click(); + await page.getByPlaceholder('Enter a name for your Design Document').fill('jurassic park'); + await page.getByPlaceholder('Enter a name for your Design Document').press('Enter'); + await page.getByTestId('project').click(); + await page.getByLabel('Files').getByLabel('jurassic park').click(); + }); - test('can delete a test suite with confirmation modal', async ({ page }) => { - await page.getByRole('button', { name: 'Create document' }).click(); - await page.getByPlaceholder('Enter a name for your Design Document').fill('jurassic park'); - await page.getByPlaceholder('Enter a name for your Design Document').press('Enter'); - await page.getByTestId('workspace-test').click(); - await page.getByText('New test suite').click(); - await page.getByLabel('Test Suites').getByLabel('Unit Test Actions').click(); - await page.getByRole('menuitemradio', { name: 'Delete suite' }).click(); - await page.locator('.modal__content').getByRole('button', { name: 'Delete' }).click(); - }); + test('can delete a test suite with confirmation modal', async ({ page }) => { + await page.getByRole('button', { name: 'Create document' }).click(); + await page.getByPlaceholder('Enter a name for your Design Document').fill('jurassic park'); + await page.getByPlaceholder('Enter a name for your Design Document').press('Enter'); + await page.getByTestId('workspace-test').click(); + await page.getByText('New test suite').click(); + await page.getByLabel('Test Suites').getByLabel('Unit Test Actions').click(); + await page.getByRole('menuitemradio', { name: 'Delete suite' }).click(); + await page.locator('.modal__content').getByRole('button', { name: 'Delete' }).click(); + }); }); diff --git a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts index 5f797c684d..f660a42e4a 100644 --- a/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/environment-editor-interactions.test.ts @@ -4,7 +4,6 @@ import { loadFixture } from '../../playwright/paths'; import { test } from '../../playwright/test'; test.describe('Environment Editor', async () => { - test.beforeEach(async ({ app, page }) => { const text = await loadFixture('environments.yaml'); await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); @@ -111,7 +110,6 @@ test.describe('Environment Editor', async () => { // NOTE - Test fails due to actual bug - the variables are not being added to the request body when the request is sent // await page.locator('pre').filter({ hasText: '| Gandalf' }).click(); - }); test('Switch to table view and edit environment', async ({ page }) => { @@ -168,5 +166,4 @@ test.describe('Environment Editor', async () => { await page.getByText('kvAnotherStr').click(); await page.getByText('12345').click(); }); - }); diff --git a/packages/insomnia-smoke-test/tests/smoke/git-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/git-interactions.test.ts index 6cabf4f145..f317e17670 100644 --- a/packages/insomnia-smoke-test/tests/smoke/git-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/git-interactions.test.ts @@ -2,116 +2,120 @@ import { test } from '../../playwright/test'; test.skip('Git Interactions (clone, checkout branch, pull, push, stage changes, ...)', async ({ page }) => { - const gitSyncSmokeTestToken = process.env.GIT_SYNC_SMOKE_TEST_TOKEN; - test.setTimeout(600000); + const gitSyncSmokeTestToken = process.env.GIT_SYNC_SMOKE_TEST_TOKEN; + test.setTimeout(600000); - // read env variable to skip test - if (!gitSyncSmokeTestToken) { - console.log('Skipping, set GIT_SYNC_SMOKE_TEST_TOKEN to run, TIP: "gh auth login to get a token" and "export GIT_SYNC_SMOKE_TEST_TOKEN=$(gh auth token)"'); - test.skip(); - return; - } + // read env variable to skip test + if (!gitSyncSmokeTestToken) { + console.log( + 'Skipping, set GIT_SYNC_SMOKE_TEST_TOKEN to run, TIP: "gh auth login to get a token" and "export GIT_SYNC_SMOKE_TEST_TOKEN=$(gh auth token)"', + ); + test.skip(); + return; + } - // generate a uuid string - const testUUID = crypto.randomUUID(); + // generate a uuid string + const testUUID = crypto.randomUUID(); - // git clone - await page.waitForSelector('[data-test-git-enable="true"]'); - await page.getByLabel('Clone git repository').click(); - await page.getByRole('tab', { name: ' Git' }).click(); - await page.getByPlaceholder('https://github.com/org/repo.git').fill('https://github.com/Kong/insomnia-git-example.git'); - await page.getByPlaceholder('Name').fill('Test User'); - await page.getByPlaceholder('Email').fill('test@test.com'); - await page.getByPlaceholder('MyUser').fill('test'); - await page.getByPlaceholder('88e7ee63b254e4b0bf047559eafe86ba9dd49507').fill(gitSyncSmokeTestToken); - await page.getByTestId('git-repository-settings-modal__sync-btn').click(); - await page.getByLabel('Toggle preview').click(); + // git clone + await page.waitForSelector('[data-test-git-enable="true"]'); + await page.getByLabel('Clone git repository').click(); + await page.getByRole('tab', { name: ' Git' }).click(); + await page + .getByPlaceholder('https://github.com/org/repo.git') + .fill('https://github.com/Kong/insomnia-git-example.git'); + await page.getByPlaceholder('Name').fill('Test User'); + await page.getByPlaceholder('Email').fill('test@test.com'); + await page.getByPlaceholder('MyUser').fill('test'); + await page.getByPlaceholder('88e7ee63b254e4b0bf047559eafe86ba9dd49507').fill(gitSyncSmokeTestToken); + await page.getByTestId('git-repository-settings-modal__sync-btn').click(); + await page.getByLabel('Toggle preview').click(); - // switch branches - await page.getByTestId('git-dropdown').click(); - await page.getByText('Branches').click(); - await page.getByLabel('main').getByText('main').click(); - await page.getByText('abc').click(); - await page.getByLabel('abc').getByRole('button', { name: 'Fetch' }).click(); - await page.getByText('abc *').click(); - await page.getByRole('heading', { name: 'Branches', exact: true }).press('Escape'); + // switch branches + await page.getByTestId('git-dropdown').click(); + await page.getByText('Branches').click(); + await page.getByLabel('main').getByText('main').click(); + await page.getByText('abc').click(); + await page.getByLabel('abc').getByRole('button', { name: 'Fetch' }).click(); + await page.getByText('abc *').click(); + await page.getByRole('heading', { name: 'Branches', exact: true }).press('Escape'); - // perform some changes and commit them - await page.locator('pre').filter({ hasText: 'title: Endpoint Security' }).click(); - await page.getByRole('textbox').fill(' test'); - // make sure the changes are stored - await page.waitForTimeout(1000); - await page.getByTestId('git-dropdown').click(); - await page.getByText('Commit').click(); - await page.getByRole('row', { name: 'spec.yaml' }).click(); - await page.locator('button[name="Stage all changes"]').click(); - await page.getByPlaceholder('This is a helpful message').click(); - await page.getByPlaceholder('This is a helpful message').fill('example commit message'); - await page.getByRole('button', { name: 'Commit', exact: true }).click(); + // perform some changes and commit them + await page.locator('pre').filter({ hasText: 'title: Endpoint Security' }).click(); + await page.getByRole('textbox').fill(' test'); + // make sure the changes are stored + await page.waitForTimeout(1000); + await page.getByTestId('git-dropdown').click(); + await page.getByText('Commit').click(); + await page.getByRole('row', { name: 'spec.yaml' }).click(); + await page.locator('button[name="Stage all changes"]').click(); + await page.getByPlaceholder('This is a helpful message').click(); + await page.getByPlaceholder('This is a helpful message').fill('example commit message'); + await page.getByRole('button', { name: 'Commit', exact: true }).click(); - // switch back to main branch, which should not have said changes - await page.getByTestId('git-dropdown').click(); - await page.getByText('main').click(); - await page.getByTestId('git-dropdown').click(); - await page.getByText('Branches').click(); - await page.getByText('main *').click(); - await page.getByText('main *').press('Escape'); - await page.getByTestId('CodeEditor').getByText('Endpoint Security').click(); + // switch back to main branch, which should not have said changes + await page.getByTestId('git-dropdown').click(); + await page.getByText('main').click(); + await page.getByTestId('git-dropdown').click(); + await page.getByText('Branches').click(); + await page.getByText('main *').click(); + await page.getByText('main *').press('Escape'); + await page.getByTestId('CodeEditor').getByText('Endpoint Security').click(); - // switch to the branch with the changes and check if they are there - await page.getByTestId('git-dropdown').click(); - await page.getByText('abc').click(); - await page.getByTestId('git-dropdown').click(); - await page.getByText('Branches').click(); - await page.getByText('abc *').click(); - await page.getByRole('heading', { name: 'Branches', exact: true }).press('Escape'); - await page.getByLabel('Toggle preview').click(); - // TODO: the following action is flaky and seems pointless, added above operation to make it work - await page.getByText('Endpoint Security test').click(); + // switch to the branch with the changes and check if they are there + await page.getByTestId('git-dropdown').click(); + await page.getByText('abc').click(); + await page.getByTestId('git-dropdown').click(); + await page.getByText('Branches').click(); + await page.getByText('abc *').click(); + await page.getByRole('heading', { name: 'Branches', exact: true }).press('Escape'); + await page.getByLabel('Toggle preview').click(); + // TODO: the following action is flaky and seems pointless, added above operation to make it work + await page.getByText('Endpoint Security test').click(); - // check git history - await page.getByTestId('git-dropdown').click(); - await page.getByText('Fetch').click(); + // check git history + await page.getByTestId('git-dropdown').click(); + await page.getByText('Fetch').click(); - await page.getByTestId('git-dropdown').click(); - await page.getByText('History').click(); - await page.getByRole('rowheader', { name: 'example commit message' }).click(); - await page.getByRole('gridcell', { name: 'just now' }).click(); - await page.getByRole('heading', { name: 'History', exact: true }).click(); - await page.getByRole('heading', { name: 'History', exact: true }).press('Escape'); + await page.getByTestId('git-dropdown').click(); + await page.getByText('History').click(); + await page.getByRole('rowheader', { name: 'example commit message' }).click(); + await page.getByRole('gridcell', { name: 'just now' }).click(); + await page.getByRole('heading', { name: 'History', exact: true }).click(); + await page.getByRole('heading', { name: 'History', exact: true }).press('Escape'); - // push changes test - await page.getByTestId('git-dropdown').click(); - await page.getByText('Branches').click(); - await page.getByLabel('push-pull-test').getByRole('button', { name: 'Fetch' }).click(); - await page.getByText('push-pull-test *').click(); - await page.getByText('push-pull-test *').press('Escape'); - await page.getByTestId('workspace-debug').click(); - await page.getByLabel('Create in collection').click(); - await page.getByLabel('New Folder').click(); - await page.getByLabel('Name', { exact: true }).click(); - await page.getByLabel('Name', { exact: true }).fill(`My Folder ${testUUID}`); - await page.getByRole('button', { name: 'Create', exact: true }).click(); - await page.getByTestId('git-dropdown').click(); + // push changes test + await page.getByTestId('git-dropdown').click(); + await page.getByText('Branches').click(); + await page.getByLabel('push-pull-test').getByRole('button', { name: 'Fetch' }).click(); + await page.getByText('push-pull-test *').click(); + await page.getByText('push-pull-test *').press('Escape'); + await page.getByTestId('workspace-debug').click(); + await page.getByLabel('Create in collection').click(); + await page.getByLabel('New Folder').click(); + await page.getByLabel('Name', { exact: true }).click(); + await page.getByLabel('Name', { exact: true }).fill(`My Folder ${testUUID}`); + await page.getByRole('button', { name: 'Create', exact: true }).click(); + await page.getByTestId('git-dropdown').click(); - // Commit changes - await page.getByText('Commit').click(); - await page.getByRole('row', { name: `My Folder ${testUUID}`, exact: true }).click(); - await page.locator('button[name="Stage all changes"]').click(); - await page.getByPlaceholder('This is a helpful message').click(); - await page.getByPlaceholder('This is a helpful message').fill(`commit test ${testUUID}`); - await page.getByRole('button', { name: 'Commit', exact: true }).click(); + // Commit changes + await page.getByText('Commit').click(); + await page.getByRole('row', { name: `My Folder ${testUUID}`, exact: true }).click(); + await page.locator('button[name="Stage all changes"]').click(); + await page.getByPlaceholder('This is a helpful message').click(); + await page.getByPlaceholder('This is a helpful message').fill(`commit test ${testUUID}`); + await page.getByRole('button', { name: 'Commit', exact: true }).click(); - // Push changes - await page.getByTestId('git-dropdown').click(); - await page.getByText('Push', { exact: true }).click(); - await page.getByTestId('git-dropdown').click(); + // Push changes + await page.getByTestId('git-dropdown').click(); + await page.getByText('Push', { exact: true }).click(); + await page.getByTestId('git-dropdown').click(); - // Check if the changes are pushed - await page.getByText('Fetch').click(); - await page.getByTestId('git-dropdown').click(); - await page.getByText('History').click(); - await page.getByRole('rowheader', { name: `commit test ${testUUID}` }).click(); - await page.getByRole('heading', { name: 'History', exact: true }).click(); - await page.getByRole('heading', { name: 'History', exact: true }).press('Escape'); + // Check if the changes are pushed + await page.getByText('Fetch').click(); + await page.getByTestId('git-dropdown').click(); + await page.getByText('History').click(); + await page.getByRole('rowheader', { name: `commit test ${testUUID}` }).click(); + await page.getByRole('heading', { name: 'History', exact: true }).click(); + await page.getByRole('heading', { name: 'History', exact: true }).press('Escape'); }); diff --git a/packages/insomnia-smoke-test/tests/smoke/global-environments.test.ts b/packages/insomnia-smoke-test/tests/smoke/global-environments.test.ts index 267af8af4a..aecc6e4035 100644 --- a/packages/insomnia-smoke-test/tests/smoke/global-environments.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/global-environments.test.ts @@ -4,50 +4,51 @@ import { loadFixture } from '../../playwright/paths'; import { test } from '../../playwright/test'; test.describe('Global Environments', async () => { + test('import and use a global environment from a collection', async ({ app, page }) => { + await loadFixtureFile('collection-for-global-environments.yaml', app, page); + await loadFixtureFile('global-environment.yaml', app, page); - test('import and use a global environment from a collection', async ({ app, page }) => { - await loadFixtureFile('collection-for-global-environments.yaml', app, page); - await loadFixtureFile('global-environment.yaml', app, page); - - await page.getByRole('link', { name: 'collection-for-global-' }).click(); - await page.getByTestId('New Request').getByLabel('GET New Request', { exact: true }).click(); - // check if it has error message - await page.getByText('Body', { exact: true }).click(); - await expect(page.getByTitle('Failed to render environment variables: _[\'global-base\']')).toHaveText('_[\'global-base\']'); - // check if it appears as a custom message when sending the request - await page.getByRole('button', { name: 'Send' }).click(); - await page.getByRole('heading', { name: '2 environment variables are' }).click(); - await page.getByRole('button', { name: 'Cancel' }).click(); - await page.getByLabel('Manage Environments').click(); - await page.getByPlaceholder('Choose a global environment').click(); - await page.getByRole('option', { name: 'global-environment' }).click(); - await page.getByText('New Environment').click(); - await page.getByTestId('underlay').click(); - await page.getByRole('button', { name: 'Send' }).click(); - await page.getByRole('tab', { name: 'Console' }).click(); - await page.locator('pre').filter({ hasText: '| 4444' }).click(); - await page.locator('pre').filter({ hasText: '| 55555' }).click(); - }); - test('create a new global environment', async ({ page }) => { - // Create new document - await page.getByRole('button', { name: 'Create document', exact: true }).click(); - await page.getByRole('button', { name: 'Create', exact: true }).click(); - await page.getByTestId('project').click(); - await page.getByLabel('Create in project').click(); - await page.getByLabel('Create', { exact: true }).getByText('Environment').click(); - await page.getByRole('button', { name: 'Create', exact: true }).click(); - await page.getByTestId('CreateEnvironmentDropdown').click(); - await page.getByText('Private environment').click(); - await page.getByLabel('Project Actions').click(); - await page.getByText('Duplicate').click(); - await page.getByText('New Environment (Copy)').click(); - }); + await page.getByRole('link', { name: 'collection-for-global-' }).click(); + await page.getByTestId('New Request').getByLabel('GET New Request', { exact: true }).click(); + // check if it has error message + await page.getByText('Body', { exact: true }).click(); + await expect(page.getByTitle("Failed to render environment variables: _['global-base']")).toHaveText( + "_['global-base']", + ); + // check if it appears as a custom message when sending the request + await page.getByRole('button', { name: 'Send' }).click(); + await page.getByRole('heading', { name: '2 environment variables are' }).click(); + await page.getByRole('button', { name: 'Cancel' }).click(); + await page.getByLabel('Manage Environments').click(); + await page.getByPlaceholder('Choose a global environment').click(); + await page.getByRole('option', { name: 'global-environment' }).click(); + await page.getByText('New Environment').click(); + await page.getByTestId('underlay').click(); + await page.getByRole('button', { name: 'Send' }).click(); + await page.getByRole('tab', { name: 'Console' }).click(); + await page.locator('pre').filter({ hasText: '| 4444' }).click(); + await page.locator('pre').filter({ hasText: '| 55555' }).click(); + }); + test('create a new global environment', async ({ page }) => { + // Create new document + await page.getByRole('button', { name: 'Create document', exact: true }).click(); + await page.getByRole('button', { name: 'Create', exact: true }).click(); + await page.getByTestId('project').click(); + await page.getByLabel('Create in project').click(); + await page.getByLabel('Create', { exact: true }).getByText('Environment').click(); + await page.getByRole('button', { name: 'Create', exact: true }).click(); + await page.getByTestId('CreateEnvironmentDropdown').click(); + await page.getByText('Private environment').click(); + await page.getByLabel('Project Actions').click(); + await page.getByText('Duplicate').click(); + await page.getByText('New Environment (Copy)').click(); + }); }); async function loadFixtureFile(fixture: string, app, page) { - const text = await loadFixture(fixture); - await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); - await page.getByLabel('Import').click(); - await page.locator('[data-test-id="import-from-clipboard"]').click(); - await page.getByRole('button', { name: 'Scan' }).click(); - await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + const text = await loadFixture(fixture); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); } diff --git a/packages/insomnia-smoke-test/tests/smoke/grpc-interactions.test.ts b/packages/insomnia-smoke-test/tests/smoke/grpc-interactions.test.ts index 9ec885fe78..df77f04f45 100644 --- a/packages/insomnia-smoke-test/tests/smoke/grpc-interactions.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/grpc-interactions.test.ts @@ -4,7 +4,6 @@ import { loadFixture } from '../../playwright/paths'; import { test } from '../../playwright/test'; test.describe('gRPC interactions', () => { - test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); let statusTag: Locator; let responseBody: Locator; @@ -85,5 +84,4 @@ test.describe('gRPC interactions', () => { await page.locator('text=Response 64').click(); await expect(responseBody).toContainText('3 Hasta Way'); }); - }); diff --git a/packages/insomnia-smoke-test/tests/smoke/insomnia-tab.test.ts b/packages/insomnia-smoke-test/tests/smoke/insomnia-tab.test.ts index eeda37e193..cd7270aaea 100644 --- a/packages/insomnia-smoke-test/tests/smoke/insomnia-tab.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/insomnia-tab.test.ts @@ -92,5 +92,4 @@ test.describe('multiple-tab feature test', () => { await tabLocator.click({ button: 'middle' }); await expect(tabLocator).toBeHidden(); }); - }); diff --git a/packages/insomnia-smoke-test/tests/smoke/insomnia-vault.test.ts b/packages/insomnia-smoke-test/tests/smoke/insomnia-vault.test.ts index fb6ef3fada..8f091b1b05 100644 --- a/packages/insomnia-smoke-test/tests/smoke/insomnia-vault.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/insomnia-vault.test.ts @@ -3,7 +3,8 @@ import { expect } from '@playwright/test'; import { loadFixture } from '../../playwright/paths'; import { test } from '../../playwright/test'; -const testVaultKey = 'eyJhbGciOiJBMjU2R0NNIiwiZXh0Ijp0cnVlLCJrIjoiaEoxaW03cjcwV3ltZ3puT3hXcDNTb0ZQS3RBaGMwcmFfd2VQb2Z2b2xRNCIsImtleV9vcHMiOlsiZW5jcnlwdCIsImRlY3J5cHQiXSwia3R5Ijoib2N0In0='; +const testVaultKey = + 'eyJhbGciOiJBMjU2R0NNIiwiZXh0Ijp0cnVlLCJrIjoiaEoxaW03cjcwV3ltZ3puT3hXcDNTb0ZQS3RBaGMwcmFfd2VQb2Z2b2xRNCIsImtleV9vcHMiOlsiZW5jcnlwdCIsImRlY3J5cHQiXSwia3R5Ijoib2N0In0='; const testVaultSalt = 'e619272433fc739d52ff1ba1b45debedfe55cb42685af10a46e2b1285acb7120'; const tesSrpSecret = 'b424e8700ef89f77a6cffc648b9c6d42bb7de58914d88cd79966684ffe5b4ebe'; @@ -94,7 +95,7 @@ test.describe('Vault key actions', async () => { // fill the input with aria lable test with valid and invalid vault key await page.getByLabel('Vault Key Input').fill('invalidVaultKey'); await page.getByRole('button', { name: 'Unlock' }).click(); - await modal.getByText('M2 didn\'t Check').click(); + await modal.getByText("M2 didn't Check").click(); }); }); @@ -154,7 +155,11 @@ test.describe('Check vault used in environment', async () => { await page.getByRole('menuitemradio', { name: 'Secret' }).click(); // go back - await page.locator('[data-icon="chevron-left"]').filter({ has: page.locator(':visible') }).first().click(); + await page + .locator('[data-icon="chevron-left"]') + .filter({ has: page.locator(':visible') }) + .first() + .click(); // activate global private vault environment await page.getByText('Vault Collection').click(); @@ -233,7 +238,7 @@ test.describe('Check vault used in environment', async () => { await page.getByTestId('underlay').click(); // activate request await page.getByTestId('legacy-invalid-vault').getByLabel('GET legacy-invalid-vault', { exact: true }).click(); - await page.getByRole('button', { name: 'Send' }).click(); // Expect to see error message + await page.getByRole('button', { name: 'Send' }).click(); // Expect to see error message await expect(page.getByText('Unexpected Request Failure')).toBeVisible(); await expect(page.getByText('vault is a reserved key for insomnia vault')).toBeVisible(); }); diff --git a/packages/insomnia-smoke-test/tests/smoke/mtls.test.ts b/packages/insomnia-smoke-test/tests/smoke/mtls.test.ts index 9e3ee4d2ff..5a202b67ca 100644 --- a/packages/insomnia-smoke-test/tests/smoke/mtls.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/mtls.test.ts @@ -1,4 +1,3 @@ - import path from 'node:path'; import { expect } from '@playwright/test'; @@ -69,5 +68,4 @@ test('can use client certificate for mTLS', async ({ app, page }) => { await page.getByRole('button', { name: 'Send', exact: true }).click(); await expect(statusTag).toContainText('401 Unauthorized'); await expect(responseBody).toContainText('Client certificate required'); - }); diff --git a/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts b/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts index a2d11557a4..ae5aecc465 100644 --- a/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/oauth.test.ts @@ -31,10 +31,7 @@ test('can make oauth2 requests', async ({ app, page }) => { await projectView.getByLabel('Request Collection').getByTestId('No PKCE').press('Enter'); await expect(page.locator('.app')).toContainText('http://127.0.0.1:4010/oidc/me'); - const [authorizationCodePage] = await Promise.all([ - app.waitForEvent('window'), - sendButton.click(), - ]); + const [authorizationCodePage] = await Promise.all([app.waitForEvent('window'), sendButton.click()]); await authorizationCodePage.waitForLoadState(); await authorizationCodePage.waitForFunction("document.cookie !== ''"); @@ -112,10 +109,7 @@ test('can make oauth2 requests', async ({ app, page }) => { await expect(page.locator('.app')).toContainText('http://127.0.0.1:4010/oidc/id-token'); await expect(page.locator('#Grant-Type')).toHaveValue('implicit'); - const [implicitPage] = await Promise.all([ - app.waitForEvent('window'), - sendButton.click(), - ]); + const [implicitPage] = await Promise.all([app.waitForEvent('window'), sendButton.click()]); await implicitPage.waitForLoadState(); await implicitPage.waitForFunction("document.cookie !== ''"); await implicitPage.locator('[name="login"]').fill('admin'); diff --git a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts index 7f8c26889d..9da11eaf9d 100644 --- a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts @@ -4,253 +4,255 @@ import { expect } from '@playwright/test'; import { Buffer } from 'buffer'; import { getFixturePath, loadFixture } from '../../playwright/paths'; -import { test } from '../../playwright/test';; +import { test } from '../../playwright/test'; test.describe('pre-request features tests', async () => { - test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); - test.beforeEach(async ({ app, page }) => { - const text = await loadFixture('pre-request-collection.yaml'); - await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + test.beforeEach(async ({ app, page }) => { + const text = await loadFixture('pre-request-collection.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); - await page.getByLabel('Import').click(); - await page.locator('[data-test-id="import-from-clipboard"]').click(); - await page.getByRole('button', { name: 'Scan' }).click(); - await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); - await page.getByLabel('Pre-request Scripts').click(); - }); + await page.getByLabel('Pre-request Scripts').click(); + }); - const testCases = [ - { - name: 'environments setting and overriding', - expectedBody: { - // fallbackToGlobal: 'fallbackToGlobal', - fallbackToBase: 'fallbackToBase', - scriptValue: 'fromEnv', - preDefinedValue: 'fromScript', - folderEnv: 'fromFolder', - }, + const testCases = [ + { + name: 'environments setting and overriding', + expectedBody: { + // fallbackToGlobal: 'fallbackToGlobal', + fallbackToBase: 'fallbackToBase', + scriptValue: 'fromEnv', + preDefinedValue: 'fromScript', + folderEnv: 'fromFolder', + }, + }, + { + name: 'variables / manipulate variables and set them to environment', + expectedBody: { + varStr: 'varStr', + varNum: 777, + varBool: true, + }, + }, + { + name: 'require / require classes from insomnia-collection module and init them', + expectedBody: { + propJson: { + disabled: false, + id: 'pid', + name: 'pname', }, - { - name: 'variables / manipulate variables and set them to environment', - expectedBody: { - varStr: 'varStr', - varNum: 777, - varBool: true, - }, + headerJson: { + key: 'headerKey', + value: 'headerValue', + id: '', + name: '', + type: '', }, - { - name: 'require / require classes from insomnia-collection module and init them', - expectedBody: { - propJson: { - disabled: false, - id: 'pid', - name: 'pname', - }, - headerJson: { - key: 'headerKey', - value: 'headerValue', - id: '', - name: '', - type: '', - }, - }, - }, - { - name: 'insomnia.request manipulation', - customVerify: (bodyJson: any) => { - expect(bodyJson.method).toEqual('GET'); - expect(bodyJson.headers['x-hello']).toEqual('hello'); - expect(bodyJson.data).toEqual('rawContent'); - }, - }, - { - name: 'insomnia.request auth manipulation (bearer)', - customVerify: (bodyJson: any) => { - const authzHeader = bodyJson.headers['authorization']; - expect(authzHeader != null).toBeTruthy(); - expect(bodyJson.headers['authorization']).toEqual('CustomTokenPrefix tokenValue'); - }, - }, - { - name: 'insomnia.request auth manipulation (basic)', - customVerify: (bodyJson: any) => { - const authzHeader = bodyJson.headers['authorization']; - expect(authzHeader != null).toBeTruthy(); - const expectedEncCred = Buffer.from('myName:myPwd', 'utf-8').toString('base64'); - expect(bodyJson.headers['authorization']).toEqual(`Basic ${expectedEncCred}`); - }, - }, - { - name: 'eval() works in script', - expectedBody: { - evalResult: 16, - }, - }, - { - name: 'require the url module', - customVerify: (bodyJson: any) => { - const reqBodyJsons = JSON.parse(bodyJson.data); - expect(reqBodyJsons).toEqual({ - hash: '#hashcontent', - host: 'insomnia.com:6666', - hostname: 'insomnia.com', - href: 'https://user:pwd@insomnia.com:6666/p1?q1=a&q2=b#hashcontent', - origin: 'https://insomnia.com:6666', - password: 'pwd', - pathname: '/p1', - port: '6666', - protocol: 'https:', - search: '?q1=a&q2=b', - username: 'user', - seachParam: 'q1=a&q2=b', - }); - }, - }, - { - name: 'require node.js modules', - expectedBody: { - path: true, - assert: true, - buffer: true, - util: true, - url: true, - punycode: true, - querystring: true, - stringDecoder: true, - stream: true, - timers: true, - events: true, - }, - }, - { - name: 'get sendRequest response through await or callback', - customVerify: (bodyJson: any) => { - const requestBody = JSON.parse(bodyJson.data); - expect(requestBody.bodyFromAwait.method).toEqual('GET'); - expect(requestBody.bodyFromCallback.method).toEqual('GET'); - }, - }, - { - name: 'require the uuid module', - expectedBody: { - uuid: '00000000-0000-0000-0000-000000000000', - }, - }, - { - name: 'require external modules and built-in lodash', - expectedBody: { - atob: true, - btoa: true, - chai: true, - cheerio: true, - crypto: true, - csv: true, - lodash: true, - moment: true, - tv4: true, - uuid: true, - xml2js: true, - builtInLodash: true, - }, - }, - { - name: 'not return until all Promise settled', - expectedBody: { - asyncTaskDone: true, - }, - }, - { - name: 'not return until all setTimeout finished', - expectedBody: { - asyncTaskDone: true, - }, - }, - { - name: 'not return until all async tasks finished', - expectedBody: { - asyncTaskDone: true, - }, - }, - { - name: 'run parent scripts only', - expectedBody: { - 'onlySetByFolderPreScript': 888, - }, - }, - { - name: 'manipulate folder envs', - expectedBody: { - 'folder1ValByReq': 1, - 'folder1ValByReqByName': 1, - 'folder2ValByReq': 2, - 'folder2ValByReqByName': 2, - 'valFound': 2, - - 'folder1ValByFolder1': 1, - 'folder1ValByFolder1ByName': 1, - 'folder2ValByFolder1': 2, - 'folder2ValByFolder1ByName': 2, - 'valFoundByFolder1': 2, - - 'folder1ValByFolder2': 1, - 'folder1ValByFolder2ByName': 1, - 'folder2ValByFolder2': 2, - 'folder2ValByFolder2ByName': 2, - 'valFoundByFolder2': 2, - }, - }, - ]; - - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; - - test(tc.name, async ({ page }) => { - const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); - const responseBody = page.getByTestId('response-pane').getByTestId('CodeEditor').locator('.CodeMirror-line'); - - await page.getByLabel('Request Collection').getByTestId(tc.name).press('Enter'); - - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - - // verify - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - - await expect(statusTag).toContainText('200 OK'); - - const rows = await responseBody.allInnerTexts(); - expect(rows.length).toBeGreaterThan(0); - - const bodyJson = JSON.parse(rows.join(' ')); - if (tc.expectedBody) { - expect(JSON.parse(bodyJson.data)).toEqual(tc.expectedBody); - } - if (tc.customVerify) { - tc.customVerify(bodyJson); - } + }, + }, + { + name: 'insomnia.request manipulation', + customVerify: (bodyJson: any) => { + expect(bodyJson.method).toEqual('GET'); + expect(bodyJson.headers['x-hello']).toEqual('hello'); + expect(bodyJson.data).toEqual('rawContent'); + }, + }, + { + name: 'insomnia.request auth manipulation (bearer)', + customVerify: (bodyJson: any) => { + const authzHeader = bodyJson.headers['authorization']; + expect(authzHeader != null).toBeTruthy(); + expect(bodyJson.headers['authorization']).toEqual('CustomTokenPrefix tokenValue'); + }, + }, + { + name: 'insomnia.request auth manipulation (basic)', + customVerify: (bodyJson: any) => { + const authzHeader = bodyJson.headers['authorization']; + expect(authzHeader != null).toBeTruthy(); + const expectedEncCred = Buffer.from('myName:myPwd', 'utf-8').toString('base64'); + expect(bodyJson.headers['authorization']).toEqual(`Basic ${expectedEncCred}`); + }, + }, + { + name: 'eval() works in script', + expectedBody: { + evalResult: 16, + }, + }, + { + name: 'require the url module', + customVerify: (bodyJson: any) => { + const reqBodyJsons = JSON.parse(bodyJson.data); + expect(reqBodyJsons).toEqual({ + hash: '#hashcontent', + host: 'insomnia.com:6666', + hostname: 'insomnia.com', + href: 'https://user:pwd@insomnia.com:6666/p1?q1=a&q2=b#hashcontent', + origin: 'https://insomnia.com:6666', + password: 'pwd', + pathname: '/p1', + port: '6666', + protocol: 'https:', + search: '?q1=a&q2=b', + username: 'user', + seachParam: 'q1=a&q2=b', }); - } + }, + }, + { + name: 'require node.js modules', + expectedBody: { + path: true, + assert: true, + buffer: true, + util: true, + url: true, + punycode: true, + querystring: true, + stringDecoder: true, + stream: true, + timers: true, + events: true, + }, + }, + { + name: 'get sendRequest response through await or callback', + customVerify: (bodyJson: any) => { + const requestBody = JSON.parse(bodyJson.data); + expect(requestBody.bodyFromAwait.method).toEqual('GET'); + expect(requestBody.bodyFromCallback.method).toEqual('GET'); + }, + }, + { + name: 'require the uuid module', + expectedBody: { + uuid: '00000000-0000-0000-0000-000000000000', + }, + }, + { + name: 'require external modules and built-in lodash', + expectedBody: { + atob: true, + btoa: true, + chai: true, + cheerio: true, + crypto: true, + csv: true, + lodash: true, + moment: true, + tv4: true, + uuid: true, + xml2js: true, + builtInLodash: true, + }, + }, + { + name: 'not return until all Promise settled', + expectedBody: { + asyncTaskDone: true, + }, + }, + { + name: 'not return until all setTimeout finished', + expectedBody: { + asyncTaskDone: true, + }, + }, + { + name: 'not return until all async tasks finished', + expectedBody: { + asyncTaskDone: true, + }, + }, + { + name: 'run parent scripts only', + expectedBody: { + onlySetByFolderPreScript: 888, + }, + }, + { + name: 'manipulate folder envs', + expectedBody: { + folder1ValByReq: 1, + folder1ValByReqByName: 1, + folder2ValByReq: 2, + folder2ValByReqByName: 2, + valFound: 2, - test('send request with content type', async ({ page }) => { - const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); - const responseBody = page.getByTestId('response-pane').getByTestId('CodeEditor').locator('.CodeMirror-line'); + folder1ValByFolder1: 1, + folder1ValByFolder1ByName: 1, + folder2ValByFolder1: 2, + folder2ValByFolder1ByName: 2, + valFoundByFolder1: 2, - await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter'); + folder1ValByFolder2: 1, + folder1ValByFolder2ByName: 1, + folder2ValByFolder2: 2, + folder2ValByFolder2ByName: 2, + valFoundByFolder2: 2, + }, + }, + ]; - // set request body - await page.getByRole('tab', { name: 'Body' }).click(); - await page.getByRole('button', { name: 'Body' }).click(); - await page.getByRole('option', { name: 'JSON' }).click(); + for (let i = 0; i < testCases.length; i++) { + const tc = testCases[i]; - const bodyEditor = page.getByTestId('CodeEditor').getByRole('textbox'); - await bodyEditor.fill('{ "rawBody": {{ _.rawBody }}, "urlencodedBody": {{ _.urlencodedBody }}, "gqlBody": {{ _.gqlBody }}, "fileBody": {{ _.fileBody }}, "formdataBody": {{ _.formdataBody }} }'); + test(tc.name, async ({ page }) => { + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + const responseBody = page.getByTestId('response-pane').getByTestId('CodeEditor').locator('.CodeMirror-line'); - // enter script - await page.getByRole('tab', { name: 'Scripts' }).click(); - const preRequestScriptEditor = page.getByTestId('CodeEditor').getByRole('textbox'); - await preRequestScriptEditor.fill(` + await page.getByLabel('Request Collection').getByTestId(tc.name).press('Enter'); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + + // verify + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + + await expect(statusTag).toContainText('200 OK'); + + const rows = await responseBody.allInnerTexts(); + expect(rows.length).toBeGreaterThan(0); + + const bodyJson = JSON.parse(rows.join(' ')); + if (tc.expectedBody) { + expect(JSON.parse(bodyJson.data)).toEqual(tc.expectedBody); + } + if (tc.customVerify) { + tc.customVerify(bodyJson); + } + }); + } + + test('send request with content type', async ({ page }) => { + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + const responseBody = page.getByTestId('response-pane').getByTestId('CodeEditor').locator('.CodeMirror-line'); + + await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter'); + + // set request body + await page.getByRole('tab', { name: 'Body' }).click(); + await page.getByRole('button', { name: 'Body' }).click(); + await page.getByRole('option', { name: 'JSON' }).click(); + + const bodyEditor = page.getByTestId('CodeEditor').getByRole('textbox'); + await bodyEditor.fill( + '{ "rawBody": {{ _.rawBody }}, "urlencodedBody": {{ _.urlencodedBody }}, "gqlBody": {{ _.gqlBody }}, "fileBody": {{ _.fileBody }}, "formdataBody": {{ _.formdataBody }} }', + ); + + // enter script + await page.getByRole('tab', { name: 'Scripts' }).click(); + const preRequestScriptEditor = page.getByTestId('CodeEditor').getByRole('textbox'); + await preRequestScriptEditor.fill(` const rawReq = { url: 'http://127.0.0.1:4010/echo', method: 'POST', @@ -341,266 +343,273 @@ test.describe('pre-request features tests', async () => { insomnia.environment.set('formdataBody', resps[4].body); `); - // TODO: wait for body and pre - request script are persisted to the disk - // should improve this part, we should avoid sync this state through db as it introduces race condition - await page.waitForTimeout(500); + // TODO: wait for body and pre - request script are persisted to the disk + // should improve this part, we should avoid sync this state through db as it introduces race condition + await page.waitForTimeout(500); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - // verify - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + // verify + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - await expect(statusTag).toContainText('200 OK'); + await expect(statusTag).toContainText('200 OK'); - const rows = await responseBody.allInnerTexts(); - expect(rows.length).toBeGreaterThan(0); + const rows = await responseBody.allInnerTexts(); + expect(rows.length).toBeGreaterThan(0); - const bodyJson = JSON.parse(rows.join(' ')); + const bodyJson = JSON.parse(rows.join(' ')); - const reqBodyJsons = JSON.parse(bodyJson.data); - expect(reqBodyJsons.rawBody.data).toEqual('rawContent'); - expect(reqBodyJsons.urlencodedBody.data).toEqual('k1=v1&k2=v2'); - expect(JSON.parse(reqBodyJsons.gqlBody.data)).toEqual({ - query: 'query', - operationName: 'operation', - variables: 'var', - }); - expect(reqBodyJsons.fileBody.data).toEqual('raw file content'); - expect(reqBodyJsons.formdataBody.data).toEqual('--X-INSOMNIA-BOUNDARY\r\nContent-Disposition: form-data; name=\"k1\"\r\n\r\nv1\r\n--X-INSOMNIA-BOUNDARY\r\nContent-Disposition: form-data; name=\"k2\"; filename=\"rawfile.txt\"\r\nContent-Type: text/plain\r\n\r\nraw file content\r\n--X-INSOMNIA-BOUNDARY--\r\n'); + const reqBodyJsons = JSON.parse(bodyJson.data); + expect(reqBodyJsons.rawBody.data).toEqual('rawContent'); + expect(reqBodyJsons.urlencodedBody.data).toEqual('k1=v1&k2=v2'); + expect(JSON.parse(reqBodyJsons.gqlBody.data)).toEqual({ + query: 'query', + operationName: 'operation', + variables: 'var', }); + expect(reqBodyJsons.fileBody.data).toEqual('raw file content'); + expect(reqBodyJsons.formdataBody.data).toEqual( + '--X-INSOMNIA-BOUNDARY\r\nContent-Disposition: form-data; name="k1"\r\n\r\nv1\r\n--X-INSOMNIA-BOUNDARY\r\nContent-Disposition: form-data; name="k2"; filename="rawfile.txt"\r\nContent-Type: text/plain\r\n\r\nraw file content\r\n--X-INSOMNIA-BOUNDARY--\r\n', + ); + }); - test('insomnia.request / update proxy configuration', async ({ page }) => { - const responsePane = page.getByTestId('response-pane'); + test('insomnia.request / update proxy configuration', async ({ page }) => { + const responsePane = page.getByTestId('response-pane'); - // update proxy configuration - await page.locator('[data-testid="settings-button"]').click(); - await page.locator('text=Insomnia Preferences').first().click(); - await page.getByRole('tab', { name: 'Proxy' }).click(); - await page.locator('text=Enable proxy').click(); - await page.locator('[name="httpProxy"]').fill('localhost:1111'); - await page.locator('[name="httpsProxy"]').fill('localhost:2222'); - await page.locator('[name="noProxy"]').fill('http://a.com,https://b.com'); - await page.locator('.app').press('Escape'); - // add 1s timeout to ensure noProxy settings is applied - INS-4155 - await page.waitForTimeout(1000); + // update proxy configuration + await page.locator('[data-testid="settings-button"]').click(); + await page.locator('text=Insomnia Preferences').first().click(); + await page.getByRole('tab', { name: 'Proxy' }).click(); + await page.locator('text=Enable proxy').click(); + await page.locator('[name="httpProxy"]').fill('localhost:1111'); + await page.locator('[name="httpsProxy"]').fill('localhost:2222'); + await page.locator('[name="noProxy"]').fill('http://a.com,https://b.com'); + await page.locator('.app').press('Escape'); + // add 1s timeout to ensure noProxy settings is applied - INS-4155 + await page.waitForTimeout(1000); - await page.getByLabel('Request Collection').getByTestId('test proxies manipulation').press('Enter'); + await page.getByLabel('Request Collection').getByTestId('test proxies manipulation').press('Enter'); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - // verify - await page.getByRole('tab', { name: 'Console' }).click(); - await expect(responsePane).toContainText('localhost:2222'); // original proxy - await expect(responsePane).toContainText('Trying 127.0.0.1:8888'); // updated proxy + // verify + await page.getByRole('tab', { name: 'Console' }).click(); + await expect(responsePane).toContainText('localhost:2222'); // original proxy + await expect(responsePane).toContainText('Trying 127.0.0.1:8888'); // updated proxy + }); + + test('update clientCertificate if request url contains tag', async ({ page }) => { + const responsePane = page.getByTestId('response-pane'); + const fixturePath = getFixturePath('certificates'); + + // update proxy configuration + await page.locator('text=Add Certificates').click(); + await page.locator('text=Add client certificate').click(); + await page.locator('[name="host"]').fill('127.0.0.1:4010'); + await page.locator('[data-key="pfx"]').click(); + + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.locator('text=Add PFX or PKCS12 file').click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(path.join(fixturePath, 'fake.pfx')); + await page.getByRole('button', { name: 'Add certificate' }).click(); + await page.getByRole('button', { name: 'Done' }).click(); + + await page + .getByLabel('Request Collection') + .getByTestId('test certificate manipulation with tagged url') + .press('Enter'); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + // verify + await page.getByRole('tab', { name: 'Console' }).click(); + await expect(responsePane).toContainText('* Adding SSL PEM certificate'); + await expect(responsePane).toContainText('Adding SSL KEY certificate'); + }); + + test('insomnia.request / update clientCertificate', async ({ page }) => { + const responsePane = page.getByTestId('response-pane'); + await page.getByLabel('Request Collection').getByTestId('test certificate manipulation').press('Enter'); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + // verify + await page.getByRole('tab', { name: 'Console' }).click(); + await expect(responsePane).toContainText('Adding SSL PEM certificate'); + await expect(responsePane).toContainText('Adding SSL KEY certificate'); + }); + + test('pre: insomnia.test and insomnia.expect can work together', async ({ page }) => { + await page.getByLabel('Request Collection').getByTestId('insomnia.test').press('Enter'); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + + // verify + await page.getByRole('tab', { name: 'Tests' }).click(); + + const responsePane = page.getByTestId('response-pane'); + await expect(responsePane).toContainText( + 'FAILunhappy tests | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200Pre-request Test', + ); + await expect(responsePane).toContainText('PASShappy tests'); + }); + + test('environment and baseEnvironment can be persisted', async ({ page }) => { + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + await page.getByLabel('Request Collection').getByTestId('persist environment').press('Enter'); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + + // verify response + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + await expect(statusTag).toContainText('200 OK'); + + // verify persisted environment + await page.getByRole('button', { name: 'Manage Environments' }).click(); + await page.getByRole('button', { name: 'Manage collection environments' }).click(); + const responseBody = page.getByRole('dialog').getByTestId('CodeEditor').locator('.CodeMirror-line'); + const rows = await responseBody.allInnerTexts(); + const bodyJson = JSON.parse(rows.join(' ')); + + expect(bodyJson).toEqual({ + // no environment is selected so the environment value will be persisted to the base environment + fromUrlValue: 'fromUrlValue', + fromEditorValue: 'fromEditorValue', + __fromScript1: 'baseEnvironment', + __fromScript2: 'collection', + __fromScript: 'environment', + examplehost: 'https://mock.insomnia.rest', + a: { + b: { + c: { + url: 'https://mock.insomnia.rest', + }, + }, + }, }); + }); - test('update clientCertificate if request url contains tag', async ({ page }) => { - const responsePane = page.getByTestId('response-pane'); - const fixturePath = getFixturePath('certificates'); + test('kv pair environment can be updated', async ({ page }) => { + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + await page.getByLabel('Request Collection').getByTestId('update kv pair environment').press('Enter'); + // switch to table view environment + await page.getByLabel('Manage Environments').click(); + await page.getByRole('button', { name: 'Manage collection environments' }).click(); + await page.getByLabel('Table Edit').click(); + await page.getByRole('button', { name: 'Close' }).click(); + await page.getByTestId('underlay').click(); - // update proxy configuration - await page.locator('text=Add Certificates').click(); - await page.locator('text=Add client certificate').click(); - await page.locator('[name="host"]').fill('127.0.0.1:4010'); - await page.locator('[data-key="pfx"]').click(); + // send request + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - const fileChooserPromise = page.waitForEvent('filechooser'); - await page.locator('text=Add PFX or PKCS12 file').click(); - const fileChooser = await fileChooserPromise; - await fileChooser.setFiles(path.join(fixturePath, 'fake.pfx')); - await page.getByRole('button', { name: 'Add certificate' }).click(); - await page.getByRole('button', { name: 'Done' }).click(); + // verify response + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + await expect(statusTag).toContainText('200 OK'); - await page.getByLabel('Request Collection').getByTestId('test certificate manipulation with tagged url').press('Enter'); + // verify table environments have been updated + await page.getByRole('button', { name: 'Manage Environments' }).click(); + await page.getByRole('button', { name: 'Manage collection environments' }).click(); + await page.getByText('__environment_type').click(); + await page.getByText('__environment_value_kv').click(); + await page.getByText('http://url-from-script').click(); + }); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - // verify - await page.getByRole('tab', { name: 'Console' }).click(); - await expect(responsePane).toContainText('* Adding SSL PEM certificate'); - await expect(responsePane).toContainText('Adding SSL KEY certificate'); - }); + test('query params should be transformed correctly', async ({ page }) => { + await page.getByLabel('Request Collection').getByTestId('testQueryParams').press('Enter'); - test('insomnia.request / update clientCertificate', async ({ page }) => { - const responsePane = page.getByTestId('response-pane'); - await page.getByLabel('Request Collection').getByTestId('test certificate manipulation').press('Enter'); + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - // verify - await page.getByRole('tab', { name: 'Console' }).click(); - await expect(responsePane).toContainText('Adding SSL PEM certificate'); - await expect(responsePane).toContainText('Adding SSL KEY certificate'); - }); + // verify response + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + await expect(statusTag).toContainText('200 OK'); - test('pre: insomnia.test and insomnia.expect can work together', async ({ page }) => { - await page.getByLabel('Request Collection').getByTestId('insomnia.test').press('Enter'); + const responsePane = page.getByTestId('response-pane'); + await page.getByRole('tab', { name: 'Console' }).click(); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - - // verify - await page.getByRole('tab', { name: 'Tests' }).click(); - - const responsePane = page.getByTestId('response-pane'); - await expect(responsePane).toContainText('FAILunhappy tests | error: AssertionError: expected 199 to deeply equal 200 | ACTUAL: 199 | EXPECTED: 200Pre-request Test'); - await expect(responsePane).toContainText('PASShappy tests'); - }); - - test('environment and baseEnvironment can be persisted', async ({ page }) => { - const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); - await page.getByLabel('Request Collection').getByTestId('persist environment').press('Enter'); - - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - - // verify response - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - await expect(statusTag).toContainText('200 OK'); - - // verify persisted environment - await page.getByRole('button', { name: 'Manage Environments' }).click(); - await page.getByRole('button', { name: 'Manage collection environments' }).click(); - const responseBody = page.getByRole('dialog').getByTestId('CodeEditor').locator('.CodeMirror-line'); - const rows = await responseBody.allInnerTexts(); - const bodyJson = JSON.parse(rows.join(' ')); - - expect(bodyJson).toEqual({ - // no environment is selected so the environment value will be persisted to the base environment - 'fromUrlValue': 'fromUrlValue', - 'fromEditorValue': 'fromEditorValue', - '__fromScript1': 'baseEnvironment', - '__fromScript2': 'collection', - '__fromScript': 'environment', - 'examplehost': 'https://mock.insomnia.rest', - 'a': { - 'b': { - 'c': { - 'url': 'https://mock.insomnia.rest', - }, - }, - }, - }); - }); - - test('kv pair environment can be updated', async ({ page }) => { - const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); - await page.getByLabel('Request Collection').getByTestId('update kv pair environment').press('Enter'); - // switch to table view environment - await page.getByLabel('Manage Environments').click(); - await page.getByRole('button', { name: 'Manage collection environments' }).click(); - await page.getByLabel('Table Edit').click(); - await page.getByRole('button', { name: 'Close' }).click(); - await page.getByTestId('underlay').click(); - - // send request - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - - // verify response - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - await expect(statusTag).toContainText('200 OK'); - - // verify table environments have been updated - await page.getByRole('button', { name: 'Manage Environments' }).click(); - await page.getByRole('button', { name: 'Manage collection environments' }).click(); - await page.getByText('__environment_type').click(); - await page.getByText('__environment_value_kv').click(); - await page.getByText('http://url-from-script').click(); - }); - - test('query params should be transformed correctly', async ({ page }) => { - await page.getByLabel('Request Collection').getByTestId('testQueryParams').press('Enter'); - - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - - // verify response - const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - await expect(statusTag).toContainText('200 OK'); - - const responsePane = page.getByTestId('response-pane'); - await page.getByRole('tab', { name: 'Console' }).click(); - - await expect(responsePane).toContainText('key=fromUrl'); - await expect(responsePane).toContainText('key=fromUrlValue'); - await expect(responsePane).toContainText('key=fromEditorValue'); - await expect(responsePane).toContainText('key=%2F'); - }); + await expect(responsePane).toContainText('key=fromUrl'); + await expect(responsePane).toContainText('key=fromUrlValue'); + await expect(responsePane).toContainText('key=fromEditorValue'); + await expect(responsePane).toContainText('key=%2F'); + }); }); test.describe('unhappy paths', async () => { - test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); - test.beforeEach(async ({ app, page }) => { - const text = await loadFixture('pre-request-collection.yaml'); - await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + test.beforeEach(async ({ app, page }) => { + const text = await loadFixture('pre-request-collection.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); - await page.getByLabel('Import').click(); - await page.locator('[data-test-id="import-from-clipboard"]').click(); - await page.getByRole('button', { name: 'Scan' }).click(); - await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); - await page.getByLabel('Pre-request Scripts').click(); - }); + await page.getByLabel('Pre-request Scripts').click(); + }); - const testCases = [ - { - name: 'custom error is returned', - preReqScript: ` + const testCases = [ + { + name: 'custom error is returned', + preReqScript: ` throw Error('my custom error'); `, - context: { - insomnia: {}, - }, - expectedResult: { - message: 'my custom error', - }, - }, - { - name: 'syntax error', - preReqScript: ` + context: { + insomnia: {}, + }, + expectedResult: { + message: 'my custom error', + }, + }, + { + name: 'syntax error', + preReqScript: ` insomnia.INVALID_FIELD.set('', '') `, - context: { - insomnia: {}, - }, - expectedResult: { - message: "Cannot read properties of undefined (reading 'set')", - }, - }, - ]; + context: { + insomnia: {}, + }, + expectedResult: { + message: "Cannot read properties of undefined (reading 'set')", + }, + }, + ]; - for (let i = 0; i < testCases.length; i++) { - const tc = testCases[i]; + for (let i = 0; i < testCases.length; i++) { + const tc = testCases[i]; - test(tc.name, async ({ page }) => { - const responsePane = page.getByTestId('response-pane'); + test(tc.name, async ({ page }) => { + const responsePane = page.getByTestId('response-pane'); - await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter'); + await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter'); - // set request body - await page.getByRole('tab', { name: 'Body' }).click(); - await page.getByRole('button', { name: 'Body' }).click(); - await page.getByRole('option', { name: 'JSON' }).click(); + // set request body + await page.getByRole('tab', { name: 'Body' }).click(); + await page.getByRole('button', { name: 'Body' }).click(); + await page.getByRole('option', { name: 'JSON' }).click(); - // enter script - await page.getByRole('tab', { name: 'Scripts' }).click(); - const preRequestScriptEditor = page.getByTestId('CodeEditor').getByRole('textbox'); - await preRequestScriptEditor.fill(tc.preReqScript); + // enter script + await page.getByRole('tab', { name: 'Scripts' }).click(); + const preRequestScriptEditor = page.getByTestId('CodeEditor').getByRole('textbox'); + await preRequestScriptEditor.fill(tc.preReqScript); - // TODO: wait for body and pre-request script are persisted to the disk - // should improve this part, we should avoid sync this state through db as it introduces race condition - await page.waitForTimeout(500); + // TODO: wait for body and pre-request script are persisted to the disk + // should improve this part, we should avoid sync this state through db as it introduces race condition + await page.waitForTimeout(500); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - // verify - await page.waitForSelector('[data-testid="response-status-tag"]:visible'); - await expect(responsePane).toContainText(tc.expectedResult.message); - }); - } + // verify + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + await expect(responsePane).toContainText(tc.expectedResult.message); + }); + } }); diff --git a/packages/insomnia-smoke-test/tests/smoke/runner.test.ts b/packages/insomnia-smoke-test/tests/smoke/runner.test.ts index 6468056d9f..9a221ef8ca 100644 --- a/packages/insomnia-smoke-test/tests/smoke/runner.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/runner.test.ts @@ -1,300 +1,274 @@ import { expect, type Page } from '@playwright/test'; import { getFixturePath, loadFixture } from '../../playwright/paths'; -import { test } from '../../playwright/test';; +import { test } from '../../playwright/test'; test.describe('runner features tests', async () => { - test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); - test.beforeEach(async ({ app, page }) => { - const text = await loadFixture('runner-collection.yaml'); - await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + test.beforeEach(async ({ app, page }) => { + const text = await loadFixture('runner-collection.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); - await page.getByLabel('Import').click(); - await page.locator('[data-test-id="import-from-clipboard"]').click(); - await page.getByRole('button', { name: 'Scan' }).click(); - await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); - await page.getByLabel('Runner').click(); - }); + await page.getByLabel('Runner').click(); + }); - const verifyResultRows = async ( - page: Page, - expectedPassed: number, - expectedSkipped: number, - expectedTotal: number, - expectedTestOrder: string[], - iteration: number = 1, - ) => { - let passedResultCount = 0; - let failedResultCount = 0; - let skippedResultCount = 0; + const verifyResultRows = async ( + page: Page, + expectedPassed: number, + expectedSkipped: number, + expectedTotal: number, + expectedTestOrder: string[], + iteration: number = 1, + ) => { + let passedResultCount = 0; + let failedResultCount = 0; + let skippedResultCount = 0; - const testResults = page.getByTestId(`runner-test-result-iteration-${iteration}`).getByTestId('test-result-row'); - const testResultCount = await testResults.count(); + const testResults = page.getByTestId(`runner-test-result-iteration-${iteration}`).getByTestId('test-result-row'); + const testResultCount = await testResults.count(); - expect(expectedTestOrder.length).toEqual(testResultCount); + expect(expectedTestOrder.length).toEqual(testResultCount); - for (let i = 0; i < testResultCount; i++) { - const resultMsg = await testResults.nth(i).textContent(); - if (resultMsg?.startsWith('PASS')) { - passedResultCount++; - } - if (resultMsg?.startsWith('FAIL')) { - failedResultCount++; - } - if (resultMsg?.startsWith('SKIP')) { - skippedResultCount++; - } + for (let i = 0; i < testResultCount; i++) { + const resultMsg = await testResults.nth(i).textContent(); + if (resultMsg?.startsWith('PASS')) { + passedResultCount++; + } + if (resultMsg?.startsWith('FAIL')) { + failedResultCount++; + } + if (resultMsg?.startsWith('SKIP')) { + skippedResultCount++; + } - const expectedResultText = expectedTestOrder[i]; - expect(resultMsg).toContain(expectedResultText); - } - expect(passedResultCount).toEqual(expectedPassed); - expect(skippedResultCount).toEqual(expectedSkipped); - expect(passedResultCount + failedResultCount + skippedResultCount).toEqual(expectedTotal); + const expectedResultText = expectedTestOrder[i]; + expect(resultMsg).toContain(expectedResultText); + } + expect(passedResultCount).toEqual(expectedPassed); + expect(skippedResultCount).toEqual(expectedSkipped); + expect(passedResultCount + failedResultCount + skippedResultCount).toEqual(expectedTotal); + }; + + test('run collection runner', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); + + // select requests to test + await page.locator('.runner-request-list-req1').click(); + await page.locator('.runner-request-list-req2').click(); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Run' }).click(); + + // verification + const verifyTestCounts = async (expectedPassed: number, expectedTotal: number) => { + await page.getByText('Req2-Pre-Check').click(); + + const testResultCounts = await page.locator('.test-result-count').allInnerTexts(); + expect(testResultCounts.length).toBe(1); + + const countParts = testResultCounts[0].split('/'); + expect(countParts.length).toBe(2); + + const summarizedPassedCount = parseInt(countParts[0], 10); + const summarizedTotalCount = parseInt(countParts[1], 10); + expect(summarizedPassedCount).toEqual(expectedPassed); + expect(summarizedTotalCount).toEqual(expectedTotal); }; + await page.getByText('6 / 8').click(); + await verifyTestCounts(6, 8); - test('run collection runner', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + const expectedTestOrder = [ + 'folder-pre-check', + 'req1-pre-check-skipped', + 'req1-pre-check', + 'folder-post-check', + 'req1-post-check', + 'expected 200 to deeply equal 201', + 'req2-pre-check', + 'req2-post-check', + ]; - // select requests to test - await page.locator('.runner-request-list-req1').click(); - await page.locator('.runner-request-list-req2').click(); + await verifyResultRows(page, 6, 1, 8, expectedTestOrder); + }); - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Run' }).click(); + test('run collection runner with data upload', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - // verification - const verifyTestCounts = async ( - expectedPassed: number, - expectedTotal: number, - ) => { - await page.getByText('Req2-Pre-Check').click(); + // upload data + await page.getByText('Upload Data').click(); + const uploadDataPath = getFixturePath('files/runner-data.json'); + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByText('Select Data File').click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(uploadDataPath); + await page.getByRole('dialog').getByText('Upload').click(); + // check iteration number match json data length + expect(await page.locator('input[name="Iterations"]').inputValue()).toBe('2'); - const testResultCounts = await page.locator('.test-result-count').allInnerTexts(); - expect(testResultCounts.length).toBe(1); + // select requests to test + await page.locator('.runner-request-list-req1').click(); + await page.locator('.runner-request-list-req2').click(); + await page.locator('.runner-request-list-req3').click(); + await page.locator('.runner-request-list-req4').click(); + await page.locator('.runner-request-list-req5').click(); - const countParts = testResultCounts[0].split('/'); - expect(countParts.length).toBe(2); + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Run' }).click(); + // check result + await page.getByText('ITERATION 1').click(); + for (let i = 1; i <= 2; i++) { + const testId = `runner-test-result-iteration-${i}`; + const iterationTestResultElement = page.getByTestId(testId); + await iterationTestResultElement.click(); + expect(iterationTestResultElement).toBeVisible(); + // req2 should be skipped from pre-request script + expect(iterationTestResultElement).not.toContainText('req2'); + } - const summarizedPassedCount = parseInt(countParts[0], 10); - const summarizedTotalCount = parseInt(countParts[1], 10); - expect(summarizedPassedCount).toEqual(expectedPassed); - expect(summarizedTotalCount).toEqual(expectedTotal); - }; - await page.getByText('6 / 8').click(); - await verifyTestCounts(6, 8); + await verifyResultRows(page, 4, 1, 6, [ + 'folder-pre-check', + 'req1-pre-check-skipped', + 'req1-pre-check', + 'folder-post-check', + 'req1-post-check', + 'expected 200 to deeply equal 201', + ]); + }); - const expectedTestOrder = [ - 'folder-pre-check', - 'req1-pre-check-skipped', - 'req1-pre-check', - 'folder-post-check', - 'req1-post-check', - 'expected 200 to deeply equal 201', - 'req2-pre-check', - 'req2-post-check', - ]; + test('run req4 3 times with setNextRequest the pre-request script', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - await verifyResultRows(page, 6, 1, 8, expectedTestOrder); - }); + await page.locator('.runner-request-list-req4').click(); - test('run collection runner with data upload', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); - // upload data - await page.getByText('Upload Data').click(); - const uploadDataPath = getFixturePath('files/runner-data.json'); - const fileChooserPromise = page.waitForEvent('filechooser'); - await page.getByText('Select Data File').click(); - const fileChooser = await fileChooserPromise; - await fileChooser.setFiles(uploadDataPath); - await page.getByRole('dialog').getByText('Upload').click(); - // check iteration number match json data length - expect(await page.locator('input[name="Iterations"]').inputValue()).toBe('2'); + // check result + await page.getByText('3 / 3').first().click(); - // select requests to test - await page.locator('.runner-request-list-req1').click(); - await page.locator('.runner-request-list-req2').click(); - await page.locator('.runner-request-list-req3').click(); - await page.locator('.runner-request-list-req4').click(); - await page.locator('.runner-request-list-req5').click(); + const expectedTestOrder = ['req4-post-check', 'req4-post-check', 'req4-post-check']; - // send - await page.getByTestId('request-pane').getByRole('button', { name: 'Run' }).click(); - // check result - await page.getByText('ITERATION 1').click(); - for (let i = 1; i <= 2; i++) { - const testId = `runner-test-result-iteration-${i}`; - const iterationTestResultElement = page.getByTestId(testId); - await iterationTestResultElement.click(); - expect(iterationTestResultElement).toBeVisible(); - // req2 should be skipped from pre-request script - expect(iterationTestResultElement).not.toContainText('req2'); - } + await verifyResultRows(page, 3, 0, 3, expectedTestOrder, 1); + }); - await verifyResultRows(page, 4, 1, 6, [ - 'folder-pre-check', - 'req1-pre-check-skipped', - 'req1-pre-check', - 'folder-post-check', - 'req1-post-check', - 'expected 200 to deeply equal 201', - ]); + test('await test works', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - }); + await page.locator('.runner-request-list-await-test').click(); - test('run req4 3 times with setNextRequest the pre-request script', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); - await page.locator('.runner-request-list-req4').click(); + // check result + await page.getByText('0 / 3').first().click(); - // send - await page.getByRole('button', { name: 'Run', exact: true }).click(); + const expectedTestOrder = ['t1', 't2', 't3']; - // check result - await page.getByText('3 / 3').first().click(); + await verifyResultRows(page, 0, 0, 3, expectedTestOrder, 1); + }); - const expectedTestOrder = [ - 'req4-post-check', - 'req4-post-check', - 'req4-post-check', - ]; + test('run req5 3 times with setNextRequest in the after-response script', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - await verifyResultRows(page, 3, 0, 3, expectedTestOrder, 1); - }); + await page.locator('.runner-request-list-req5').click(); - test('await test works', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); - await page.locator('.runner-request-list-await-test').click(); + // check result + await page.getByText('3 / 3').first().click(); - // send - await page.getByRole('button', { name: 'Run', exact: true }).click(); + const expectedTestOrder = ['req5-post-check', 'req5-post-check', 'req5-post-check']; - // check result - await page.getByText('0 / 3').first().click(); + await verifyResultRows(page, 3, 0, 3, expectedTestOrder, 1); + }); - const expectedTestOrder = [ - 't1', - 't2', - 't3', - ]; + test('skip req01 with setNextRequest', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - await verifyResultRows(page, 0, 0, 3, expectedTestOrder, 1); - }); + await page.locator('.runner-request-list-req0').click(); + await page.locator('.runner-request-list-req01').click(); + await page.locator('.runner-request-list-req02').click(); - test('run req5 3 times with setNextRequest in the after-response script', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); - await page.locator('.runner-request-list-req5').click(); + // check result + await page.getByText('1 / 2').first().click(); - // send - await page.getByRole('button', { name: 'Run', exact: true }).click(); + const expectedTestOrder = [ + 'req0-post-check', + // 'req01-post-check' is skipped + 'req02-post-check', + ]; - // check result - await page.getByText('3 / 3').first().click(); + await verifyResultRows(page, 1, 1, 2, expectedTestOrder, 1); + }); - const expectedTestOrder = [ - 'req5-post-check', - 'req5-post-check', - 'req5-post-check', - ]; + test('can read variables during whole execution', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - await verifyResultRows(page, 3, 0, 3, expectedTestOrder, 1); - }); + await page.locator('.runner-request-list-set-var1').click(); + await page.locator('.runner-request-list-read-var1').click(); - test('skip req01 with setNextRequest', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); - await page.locator('.runner-request-list-req0').click(); - await page.locator('.runner-request-list-req01').click(); - await page.locator('.runner-request-list-req02').click(); + // check result + await page.getByText('3 / 3').first().click(); - // send - await page.getByRole('button', { name: 'Run', exact: true }).click(); + const expectedTestOrder = ['set-var1-check', 'read-var1-pre-check', 'read-var1-post-check']; - // check result - await page.getByText('1 / 2').first().click(); + await verifyResultRows(page, 3, 0, 3, expectedTestOrder, 1); + }); - const expectedTestOrder = [ - 'req0-post-check', - // 'req01-post-check' is skipped - 'req02-post-check', - ]; + test('can detect sync and async test failure', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - await verifyResultRows(page, 1, 1, 2, expectedTestOrder, 1); - }); + await page.locator('.runner-request-list-async-test').click(); - test('can read variables during whole execution', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); - await page.locator('.runner-request-list-set-var1').click(); - await page.locator('.runner-request-list-read-var1').click(); + // check result + await page.getByText('0 / 4').first().click(); - // send - await page.getByRole('button', { name: 'Run', exact: true }).click(); + const expectedTestOrder = ['sync_pre_test', 'async_pre_test', 'sync_post_test', 'async_post_test']; - // check result - await page.getByText('3 / 3').first().click(); + await verifyResultRows(page, 0, 0, 4, expectedTestOrder, 1); + }); - const expectedTestOrder = [ - 'set-var1-check', - 'read-var1-pre-check', - 'read-var1-post-check', - ]; + test('settings: can turn off logs', async ({ page }) => { + await page.getByTestId('run-collection-btn-quick').click(); - await verifyResultRows(page, 3, 0, 3, expectedTestOrder, 1); - }); + await page.locator('.runner-request-list-printLogs').click(); - test('can detect sync and async test failure', async ({ page }) => { - await page.getByTestId('run-collection-btn-quick').click(); + const expectToHaveLogs = [false, true]; - await page.locator('.runner-request-list-async-test').click(); + for (const expectToHaveLog of expectToHaveLogs) { + // configure + await page.getByRole('tab', { name: 'advanced' }).click(); + await page.locator('input[name="enable-log"]').click(); - // send - await page.getByRole('button', { name: 'Run', exact: true }).click(); + // send + await page.getByRole('button', { name: 'Run', exact: true }).click(); - // check result - await page.getByText('0 / 4').first().click(); + // verify there's no log + await page.getByText('1 / 1').first().click(); + await page.getByRole('tab', { name: 'Console' }).click(); - const expectedTestOrder = [ - 'sync_pre_test', - 'async_pre_test', - 'sync_post_test', - 'async_post_test', - ]; - - await verifyResultRows(page, 0, 0, 4, expectedTestOrder, 1); - }); - - test('settings: can turn off logs', async ({ page }) => { - - await page.getByTestId('run-collection-btn-quick').click(); - - await page.locator('.runner-request-list-printLogs').click(); - - const expectToHaveLogs = [false, true]; - - for (const expectToHaveLog of expectToHaveLogs) { - // configure - await page.getByRole('tab', { name: 'advanced' }).click(); - await page.locator('input[name="enable-log"]').click(); - - // send - await page.getByRole('button', { name: 'Run', exact: true }).click(); - - // verify there's no log - await page.getByText('1 / 1').first().click(); - await page.getByRole('tab', { name: 'Console' }).click(); - - const consoleTabContent = page.locator('.pane-two'); - if (expectToHaveLog) { - await expect(consoleTabContent).toContainText("it won't print"); - } else { - await expect(consoleTabContent).not.toContainText("it won't print"); - } - } - }); + const consoleTabContent = page.locator('.pane-two'); + if (expectToHaveLog) { + await expect(consoleTabContent).toContainText("it won't print"); + } else { + await expect(consoleTabContent).not.toContainText("it won't print"); + } + } + }); }); diff --git a/packages/insomnia-testing/src/generate/generate.ts b/packages/insomnia-testing/src/generate/generate.ts index 3e98bd6435..4c4d6273d0 100644 --- a/packages/insomnia-testing/src/generate/generate.ts +++ b/packages/insomnia-testing/src/generate/generate.ts @@ -30,10 +30,7 @@ export const generate = (suites: TestSuite[]) => { return lines.join('\n'); }; -export const generateToFile = async ( - filepath: string, - suites: TestSuite[], -) => { +export const generateToFile = async (filepath: string, suites: TestSuite[]) => { return new Promise((resolve, reject) => { const js = generate(suites); return writeFile(filepath, js, err => { @@ -46,10 +43,7 @@ export const generateToFile = async ( }); }; -const generateSuiteLines = ( - n: number, - suite?: TestSuite | null, -) => { +const generateSuiteLines = (n: number, suite?: TestSuite | null) => { if (!suite) { return []; } @@ -98,9 +92,7 @@ const generateTestLines = (num: number, test?: Test | null) => { if (typeof defaultRequestId === 'string') { lines.push(indent(num, '// Set active request on global insomnia object')); - lines.push( - indent(num, `insomnia.setActiveRequestId('${defaultRequestId}');`), - ); + lines.push(indent(num, `insomnia.setActiveRequestId('${defaultRequestId}');`)); } // Add user-defined test source diff --git a/packages/insomnia-testing/src/generate/index.ts b/packages/insomnia-testing/src/generate/index.ts index 97674e02d3..d067027158 100644 --- a/packages/insomnia-testing/src/generate/index.ts +++ b/packages/insomnia-testing/src/generate/index.ts @@ -1,8 +1,5 @@ import type { Test, TestSuite } from './generate'; -export { - generate, - generateToFile, -} from './generate'; +export { generate, generateToFile } from './generate'; export type { Test, TestSuite }; diff --git a/packages/insomnia-testing/src/generate/util.test.ts b/packages/insomnia-testing/src/generate/util.test.ts index 1760cb4fb4..44c46908d5 100644 --- a/packages/insomnia-testing/src/generate/util.test.ts +++ b/packages/insomnia-testing/src/generate/util.test.ts @@ -15,10 +15,8 @@ describe('util', () => { }); it('indents multi-line blocks', () => { - const text = 'function greet() {\n console.log(\'Hello World!\');\n}'; - expect(indent(1, text)).toBe( - ' function greet() {\n console.log(\'Hello World!\');\n }', - ); + const text = "function greet() {\n console.log('Hello World!');\n}"; + expect(indent(1, text)).toBe(" function greet() {\n console.log('Hello World!');\n }"); }); }); diff --git a/packages/insomnia-testing/src/generate/util.ts b/packages/insomnia-testing/src/generate/util.ts index 45733eb9bf..61f63864d7 100644 --- a/packages/insomnia-testing/src/generate/util.ts +++ b/packages/insomnia-testing/src/generate/util.ts @@ -1,5 +1,5 @@ export const escapeJsStr = (s: string) => { - return s.replace(/'/g, '\\\''); + return s.replace(/'/g, "\\'"); }; export const indent = (level: number, code: string) => { diff --git a/packages/insomnia-testing/src/index.ts b/packages/insomnia-testing/src/index.ts index 16f646581e..94c4866be4 100644 --- a/packages/insomnia-testing/src/index.ts +++ b/packages/insomnia-testing/src/index.ts @@ -1,13 +1,7 @@ import type { Test, TestSuite } from './generate'; import type { TestResults } from './run'; -export { - generate, - generateToFile, -} from './generate'; +export { generate, generateToFile } from './generate'; -export { - runTests, - runTestsCli, -} from './run'; +export { runTests, runTestsCli } from './run'; export type { Test, TestSuite, TestResults }; diff --git a/packages/insomnia-testing/src/integration/integration.test.ts b/packages/insomnia-testing/src/integration/integration.test.ts index 3afec7749f..d5b2d74627 100644 --- a/packages/insomnia-testing/src/integration/integration.test.ts +++ b/packages/insomnia-testing/src/integration/integration.test.ts @@ -1,4 +1,3 @@ - import { describe, expect, it } from 'vitest'; import { vi } from 'vitest'; @@ -14,8 +13,7 @@ describe('integration', () => { tests: [ { name: 'should return -1 when the value is not present', - code: - 'expect([1, 2, 3].indexOf(4)).to.equal(-1);\nexpect(true).to.equal(true);', + code: 'expect([1, 2, 3].indexOf(4)).to.equal(-1);\nexpect(true).to.equal(true);', defaultRequestId: null, }, { @@ -43,8 +41,7 @@ describe('integration', () => { tests: [ { name: 'should return -1 when the value is not present', - code: - 'expect([1, 2, 3].indexOf(4)).to.equal(-1);\nexpect(true).to.be.true;', + code: 'expect([1, 2, 3].indexOf(4)).to.equal(-1);\nexpect(true).to.be.true;', defaultRequestId: null, }, { @@ -95,9 +92,9 @@ describe('integration', () => { name: 'Tests referencing request by ID', defaultRequestId: null, code: [ - 'const resp = await insomnia.send(\'foo\');', + "const resp = await insomnia.send('foo');", 'expect(resp.status).to.equal(200);', - 'expect(resp.statusMessage).to.equal(\'abc\');', + "expect(resp.statusMessage).to.equal('abc');", ].join('\n'), }, { @@ -106,7 +103,7 @@ describe('integration', () => { code: [ 'const resp = await insomnia.send();', 'expect(resp.status).to.equal(301);', - 'expect(resp.statusMessage).to.equal(\'def\');', + "expect(resp.statusMessage).to.equal('def');", ].join('\n'), }, ], diff --git a/packages/insomnia-testing/src/run/index.ts b/packages/insomnia-testing/src/run/index.ts index 464d833f7d..19a7c5faf0 100644 --- a/packages/insomnia-testing/src/run/index.ts +++ b/packages/insomnia-testing/src/run/index.ts @@ -1,9 +1,4 @@ import type { TestResults } from './entities'; -export { - runTests, - runTestsCli, -} from './run'; +export { runTests, runTestsCli } from './run'; -export type { - TestResults, -}; +export type { TestResults }; diff --git a/packages/insomnia-testing/src/run/javascript-reporter.ts b/packages/insomnia-testing/src/run/javascript-reporter.ts index 1aef2e9ee6..6c3939acef 100644 --- a/packages/insomnia-testing/src/run/javascript-reporter.ts +++ b/packages/insomnia-testing/src/run/javascript-reporter.ts @@ -3,8 +3,8 @@ * * https://github.com/mochajs/mocha/blob/9d4a8ec2d22ee154aecb1f8eeb25af8e6309faa8/lib/reporters/json.js */ -import type { Runnable, Runner, Test } from 'mocha'; -import Mocha, { type MochaOptions, reporters } from 'mocha'; +import type { MochaOptions, Runnable, Runner, Test } from 'mocha'; +import Mocha, { reporters } from 'mocha'; import type { TestResult, TestResults } from './entities'; diff --git a/packages/insomnia-testing/src/run/run.test.ts b/packages/insomnia-testing/src/run/run.test.ts index 6552f351a6..8c084b370b 100644 --- a/packages/insomnia-testing/src/run/run.test.ts +++ b/packages/insomnia-testing/src/run/run.test.ts @@ -28,7 +28,8 @@ describe('Example', () => { `; describe('run', () => { - const getMockedSendRequest = () => vi.fn>().mockResolvedValue({ status: 200 }); + const getMockedSendRequest = () => + vi.fn>().mockResolvedValue({ status: 200 }); it('runs a mocha suite', async () => { const { stats } = await runTests(exampleTest, { sendRequest: getMockedSendRequest() }); @@ -54,10 +55,7 @@ describe('run', () => { it('calls sendRequest() callback', async () => { const sendRequest = getMockedSendRequest(); - const { stats } = await runTests( - exampleTestWithRequest, - { sendRequest }, - ); + const { stats } = await runTests(exampleTestWithRequest, { sendRequest }); expect(sendRequest).toHaveBeenCalledWith('req_123'); expect(stats.passes).toBe(1); diff --git a/packages/insomnia-testing/src/run/run.ts b/packages/insomnia-testing/src/run/run.ts index dcdbf953d7..df1ffc132e 100644 --- a/packages/insomnia-testing/src/run/run.ts +++ b/packages/insomnia-testing/src/run/run.ts @@ -1,12 +1,14 @@ import chai from 'chai'; import { unlink, writeFileSync } from 'fs'; import fs from 'fs'; -import Mocha, { type Reporter, type ReporterConstructor } from 'mocha'; +import type { Reporter, ReporterConstructor } from 'mocha'; +import Mocha from 'mocha'; import { tmpdir } from 'os'; import { join } from 'path'; import type { TestResults } from './entities'; -import { Insomnia, type InsomniaOptions } from './insomnia'; +import type { InsomniaOptions } from './insomnia'; +import { Insomnia } from './insomnia'; import { JavaScriptReporter } from './javascript-reporter'; // declare var insomnia: Insomnia; @@ -15,61 +17,61 @@ const runInternal = async ( options: InsomniaOptions, reporter: Reporter | ReporterConstructor = 'spec', extractResult: (runner: Mocha.Runner) => TReturn, -) => new Promise((resolve, reject) => { - const { bail, keepFile, testFilter } = options; +) => + new Promise((resolve, reject) => { + const { bail, keepFile, testFilter } = options; - // Add global `insomnia` helper. - // This is the only way to add new globals to the Mocha environment as far as I can tell - // @ts-expect-error -- global hack - global.insomnia = new Insomnia(options); + // Add global `insomnia` helper. + // This is the only way to add new globals to the Mocha environment as far as I can tell + // @ts-expect-error -- global hack + global.insomnia = new Insomnia(options); - - chai.use(require('chai-json-schema')); - // @ts-expect-error -- global hack - global.chai = chai; + chai.use(require('chai-json-schema')); + // @ts-expect-error -- global hack + global.chai = chai; - const mocha: Mocha = new Mocha({ - // ms * sec * min - timeout: 1000 * 60 * 1, - globals: ['insomnia', 'chai'], - bail, - reporter, - fgrep: testFilter, - }); + const mocha: Mocha = new Mocha({ + // ms * sec * min + timeout: 1000 * 60 * 1, + globals: ['insomnia', 'chai'], + bail, + reporter, + fgrep: testFilter, + }); - const sources = Array.isArray(testSrc) ? testSrc : [testSrc]; - sources.forEach(source => { - mocha.addFile(writeTempFile(source)); - }); + const sources = Array.isArray(testSrc) ? testSrc : [testSrc]; + sources.forEach(source => { + mocha.addFile(writeTempFile(source)); + }); - try { - const runner = mocha.run(() => { - resolve(extractResult(runner)); + try { + const runner = mocha.run(() => { + resolve(extractResult(runner)); - // Remove global since we don't need it anymore - // @ts-expect-error -- global hack - delete global.insomnia; - // @ts-expect-error -- global hack - delete global.chai; + // Remove global since we don't need it anymore + // @ts-expect-error -- global hack + delete global.insomnia; + // @ts-expect-error -- global hack + delete global.chai; - if (keepFile && mocha.files.length) { - console.log(`Test files: ${JSON.stringify(mocha.files)}.`); - return; - } + if (keepFile && mocha.files.length) { + console.log(`Test files: ${JSON.stringify(mocha.files)}.`); + return; + } - // Clean up temp files - mocha.files.forEach(file => { - unlink(file, err => { - if (err) { - console.log('Failed to clean up test file', file, err); - } + // Clean up temp files + mocha.files.forEach(file => { + unlink(file, err => { + if (err) { + console.log('Failed to clean up test file', file, err); + } + }); }); }); - }); - } catch (err) { - reject(err); - } -}); + } catch (err) { + reject(err); + } + }); /** * Copy test to tmp dir and return the file path @@ -93,12 +95,7 @@ type CliOptions = InsomniaOptions & { export const runTestsCli = async ( testSrc: string | string[], { reporter, ...options }: CliOptions, -) => runInternal( - testSrc, - options, - reporter, - runner => !runner.stats?.failures, -); +) => runInternal(testSrc, options, reporter, runner => !runner.stats?.failures); /** * Run a test file using Mocha and returns JS results @@ -106,10 +103,11 @@ export const runTestsCli = async ( export const runTests = async ( testSrc: string | string[], options: InsomniaOptions, -) => runInternal( - testSrc, - options, - JavaScriptReporter, - // @ts-expect-error the `testResults` property is added onto the runner by the JavascriptReporter - runner => runner.testResults as TestResults, -); +) => + runInternal( + testSrc, + options, + JavaScriptReporter, + // @ts-expect-error the `testResults` property is added onto the runner by the JavascriptReporter + runner => runner.testResults as TestResults, + ); diff --git a/packages/insomnia-testing/tsconfig.json b/packages/insomnia-testing/tsconfig.json index fcb163244c..75657383e2 100644 --- a/packages/insomnia-testing/tsconfig.json +++ b/packages/insomnia-testing/tsconfig.json @@ -17,8 +17,6 @@ "module": "preserve", "noEmit": true, /* If your code runs in the DOM: */ - "lib": [ - "es2022", - ], + "lib": ["es2022"] } } diff --git a/packages/insomnia/README.md b/packages/insomnia/README.md index 6790a09c69..60a3fd53a9 100644 --- a/packages/insomnia/README.md +++ b/packages/insomnia/README.md @@ -9,7 +9,7 @@ The main desktop application. - For real-time data fetching we use SSE (Server-Sent Events). This is a standard way to receive data from the server in real-time. The server will send a message to the client when something happens. The client will receive the message and update the data accordingly. To handle SSE we use the `insomnia-event-source://` protocol which is a custom protocol that gets handled by the app in the main process. This allows us to overcome cross-origin issues and also to handle SSE in a centralized way. - Polling is also used to fetch data from the server. This is used when we need to fetch data periodically as SSE can sometimes fail and data can go out of sync. -The main places we use polling are: + The main places we use polling are: - Refreshing Insomnia Sync data. - Refreshing Git Sync data. - Refreshing Presence data. diff --git a/packages/insomnia/customSign.js b/packages/insomnia/customSign.js index af2c68e7ba..c14d82f8c5 100644 --- a/packages/insomnia/customSign.js +++ b/packages/insomnia/customSign.js @@ -8,31 +8,31 @@ const execAsync = util.promisify(exec); // through hooking into the signing step of electron-builder while the final squirrel installer is being built // This makes it possible to sign the Update.exe and stub of Insomnia.exe that end up in C:\Users\\AppData\Local\insomnia exports.default = async function (configuration) { - // skip signing if not windows squirrel - if (configuration.options.target.length === 0 || configuration.options.target[0].target !== 'squirrel') { - console.log('[customSign] Skipping signing because target is not windows squirrel.'); - return; - } + // skip signing if not windows squirrel + if (configuration.options.target.length === 0 || configuration.options.target[0].target !== 'squirrel') { + console.log('[customSign] Skipping signing because target is not windows squirrel.'); + return; + } - const { USERNAME, PASSWORD, CREDENTIAL_ID, TOTP_SECRET } = process.env; - if (!USERNAME || !PASSWORD || !CREDENTIAL_ID || !TOTP_SECRET) { - console.log('[customSign] Skipping signing, Missing required environment variables.'); - return; - } + const { USERNAME, PASSWORD, CREDENTIAL_ID, TOTP_SECRET } = process.env; + if (!USERNAME || !PASSWORD || !CREDENTIAL_ID || !TOTP_SECRET) { + console.log('[customSign] Skipping signing, Missing required environment variables.'); + return; + } - // Note: Avoid changing the lines bellow. Risk of breaking the windows code-signing process. - // Feedback loop > 15 mins. Requires a branch on origin, a PR, and a separate dummy release pipeline to test changes. - // sslcom/codesigner-win has large image size (>1GB) and requires docker within windows-latest host. - const rawPath = configuration.path.replace(/(\r\n|\n|\r)/gm, ''); // remove /n and other crap from path - console.log('[customSign] File to sign before final packaging:', rawPath); - const absolutePath = path.resolve(rawPath); // C:\Users\...\Update.exe - const fixedAbsolutePath = absolutePath.replace(/\\/g, '/'); // C:/Users/.../Update.exe - const lastSlashIndex = fixedAbsolutePath.lastIndexOf('/'); // index of last / slash - const directoryPath = fixedAbsolutePath.substring(0, lastSlashIndex); // C:/Users/... - const inputFileName = path.basename(absolutePath); // Update.exe - const codeSignPath = 'C:/CodeSignTool/Insomnia'; // path inside docker container - const dockerInputFilePath = path.join(codeSignPath, inputFileName); // C:/CodeSignTool/Insomnia/Update.exe - const dockerCommand = `docker run --rm \ + // Note: Avoid changing the lines bellow. Risk of breaking the windows code-signing process. + // Feedback loop > 15 mins. Requires a branch on origin, a PR, and a separate dummy release pipeline to test changes. + // sslcom/codesigner-win has large image size (>1GB) and requires docker within windows-latest host. + const rawPath = configuration.path.replace(/(\r\n|\n|\r)/gm, ''); // remove /n and other crap from path + console.log('[customSign] File to sign before final packaging:', rawPath); + const absolutePath = path.resolve(rawPath); // C:\Users\...\Update.exe + const fixedAbsolutePath = absolutePath.replace(/\\/g, '/'); // C:/Users/.../Update.exe + const lastSlashIndex = fixedAbsolutePath.lastIndexOf('/'); // index of last / slash + const directoryPath = fixedAbsolutePath.substring(0, lastSlashIndex); // C:/Users/... + const inputFileName = path.basename(absolutePath); // Update.exe + const codeSignPath = 'C:/CodeSignTool/Insomnia'; // path inside docker container + const dockerInputFilePath = path.join(codeSignPath, inputFileName); // C:/CodeSignTool/Insomnia/Update.exe + const dockerCommand = `docker run --rm \ -v "${directoryPath}:${codeSignPath}" \ -e USERNAME="${USERNAME}" \ -e PASSWORD="${PASSWORD}" \ @@ -41,19 +41,19 @@ exports.default = async function (configuration) { ghcr.io/sslcom/codesigner-win:latest sign \ \`\`-input_file_path="${dockerInputFilePath}" \`\`-override`; - try { - console.log('[customSign] Docker command:', dockerCommand); - console.log('[customSign] Starting to run sign cmd via docker...'); - const { stdout, stderr } = await execAsync(dockerCommand); + try { + console.log('[customSign] Docker command:', dockerCommand); + console.log('[customSign] Starting to run sign cmd via docker...'); + const { stdout, stderr } = await execAsync(dockerCommand); - console.log('[customSign] Docker command output:', stdout); - if (stderr) { - console.error('[customSign] Docker command error output:', stderr); - } - - console.log('[customSign] File signed successfully.'); - } catch (error) { - console.error('[customSign] Error executing Docker command:', error); - throw error; + console.log('[customSign] Docker command output:', stdout); + if (stderr) { + console.error('[customSign] Docker command error output:', stderr); } + + console.log('[customSign] File signed successfully.'); + } catch (error) { + console.error('[customSign] Error executing Docker command:', error); + throw error; + } }; diff --git a/packages/insomnia/electron-builder.config.js b/packages/insomnia/electron-builder.config.js index 1f806967cc..a913c3b765 100644 --- a/packages/insomnia/electron-builder.config.js +++ b/packages/insomnia/electron-builder.config.js @@ -58,9 +58,7 @@ const config = { }, // If this step fails its possible apple has new license terms which need to be accepted by logging into https://developer.apple.com/account notarize: true, - asarUnpack: [ - 'node_modules/@getinsomnia/node-libcurl', - ], + asarUnpack: ['node_modules/@getinsomnia/node-libcurl'], }, dmg: { window: { @@ -88,13 +86,12 @@ const config = { ], signtoolOptions: { sign: './customSign.js', - signingHashAlgorithms: ['sha256'], // avoid duplicate signing hook calls https://github.com/electron-userland/electron-builder/issues/3995#issuecomment-505725704 + signingHashAlgorithms: ['sha256'], // avoid duplicate signing hook calls https://github.com/electron-userland/electron-builder/issues/3995#issuecomment-505725704 }, }, squirrelWindows: { artifactName: `${BINARY_PREFIX}-\${version}.\${ext}`, - iconUrl: - 'https://github.com/kong/insomnia/blob/develop/packages/insomnia/src/icons/icon.ico?raw=true', + iconUrl: 'https://github.com/kong/insomnia/blob/develop/packages/insomnia/src/icons/icon.ico?raw=true', }, portable: { artifactName: `${BINARY_PREFIX}-\${version}-portable.\${ext}`, @@ -111,7 +108,7 @@ const config = { Comment: 'Insomnia is a cross-platform REST client, built on top of Electron.', Categories: 'Development', Keywords: 'GraphQL;REST;gRPC;SOAP;openAPI;GitOps;', - } + }, }, target: [ { @@ -135,20 +132,23 @@ const config = { // Prevents RPM from packaging build-id metadata, some of which is the // same across all electron-builder applications, which causes package // conflicts - fpm: [ - '--rpm-rpmbuild-define=_build_id_links none', - ], + fpm: ['--rpm-rpmbuild-define=_build_id_links none'], }, snap: { base: 'core22', }, }; -const { env: { BUILD_TARGETS }, platform } = process; +const { + env: { BUILD_TARGETS }, + platform, +} = process; const targets = BUILD_TARGETS?.split(','); if (platform && targets) { console.log('overriding build targets to: ', targets); const PLATFORM_MAP = { darwin: 'mac', linux: 'linux', win32: 'win' }; - config[PLATFORM_MAP[platform]].target = config[PLATFORM_MAP[platform]].target.filter(({ target }) => targets.includes(target)); + config[PLATFORM_MAP[platform]].target = config[PLATFORM_MAP[platform]].target.filter(({ target }) => + targets.includes(target), + ); } module.exports = config; diff --git a/packages/insomnia/esbuild.main.ts b/packages/insomnia/esbuild.main.ts index 81549e94cf..c7a1f22452 100644 --- a/packages/insomnia/esbuild.main.ts +++ b/packages/insomnia/esbuild.main.ts @@ -13,27 +13,21 @@ export default async function build(options: Options) { const __DEV__ = mode !== 'production'; const PORT = pkg.dev['dev-server-port']; - const outdir = __DEV__ - ? path.join(__dirname, 'src') - : path.join(__dirname, 'build'); + const outdir = __DEV__ ? path.join(__dirname, 'src') : path.join(__dirname, 'build'); const env: Record = __DEV__ ? { - 'process.env.APP_RENDER_URL': JSON.stringify( - `http://localhost:${PORT}/index.html` - ), - 'process.env.HIDDEN_BROWSER_WINDOW_URL': JSON.stringify( - `http://localhost:${PORT}/hidden-window.html` - ), - 'process.env.NODE_ENV': JSON.stringify('development'), - 'process.env.INSOMNIA_ENV': JSON.stringify('development'), - 'process.env.BUILD_DATE': JSON.stringify(new Date()), - } + 'process.env.APP_RENDER_URL': JSON.stringify(`http://localhost:${PORT}/index.html`), + 'process.env.HIDDEN_BROWSER_WINDOW_URL': JSON.stringify(`http://localhost:${PORT}/hidden-window.html`), + 'process.env.NODE_ENV': JSON.stringify('development'), + 'process.env.INSOMNIA_ENV': JSON.stringify('development'), + 'process.env.BUILD_DATE': JSON.stringify(new Date()), + } : { - 'process.env.NODE_ENV': JSON.stringify('production'), - 'process.env.INSOMNIA_ENV': JSON.stringify('production'), - 'process.env.BUILD_DATE': JSON.stringify(new Date()), - }; + 'process.env.NODE_ENV': JSON.stringify('production'), + 'process.env.INSOMNIA_ENV': JSON.stringify('production'), + 'process.env.BUILD_DATE': JSON.stringify(new Date()), + }; const preload = esbuild.build({ entryPoints: ['./src/preload.ts'], outfile: path.join(outdir, 'preload.js'), @@ -76,18 +70,13 @@ export default async function build(options: Options) { ], }); - return Promise.all([ - main, - preload, - hiddenBrowserWindowPreload, - ]); + return Promise.all([main, preload, hiddenBrowserWindowPreload]); } // Build if ran as a cli script const isMain = require.main === module; if (isMain) { - const mode = - process.env.NODE_ENV === 'development' ? 'development' : 'production'; + const mode = process.env.NODE_ENV === 'development' ? 'development' : 'production'; build({ mode }); } diff --git a/packages/insomnia/postcss.config.js b/packages/insomnia/postcss.config.js index cdbe50f3a4..df56d11471 100644 --- a/packages/insomnia/postcss.config.js +++ b/packages/insomnia/postcss.config.js @@ -1,7 +1,7 @@ module.exports = { plugins: { 'tailwindcss/nesting': {}, - tailwindcss: {}, - autoprefixer: {}, + 'tailwindcss': {}, + 'autoprefixer': {}, }, }; diff --git a/packages/insomnia/scripts/build.ts b/packages/insomnia/scripts/build.ts index d377172072..a0af80f70a 100644 --- a/packages/insomnia/scripts/build.ts +++ b/packages/insomnia/scripts/build.ts @@ -20,12 +20,8 @@ if (require.main === module) { export const start = async () => { console.log('[build] Starting build'); - console.log( - `[build] npm: ${childProcess.spawnSync('npm', ['--version']).stdout}`.trim() - ); - console.log( - `[build] node: ${childProcess.spawnSync('node', ['--version']).stdout}`.trim() - ); + console.log(`[build] npm: ${childProcess.spawnSync('npm', ['--version']).stdout}`.trim()); + console.log(`[build] node: ${childProcess.spawnSync('node', ['--version']).stdout}`.trim()); if (process.version.indexOf('v22.') !== 0) { console.log('[build] Node 22.x.x is required to build'); diff --git a/packages/insomnia/send-request/electron/index.js b/packages/insomnia/send-request/electron/index.js index 5c3240e5e7..a338fd35d7 100644 --- a/packages/insomnia/send-request/electron/index.js +++ b/packages/insomnia/send-request/electron/index.js @@ -1,7 +1,8 @@ // This file implements just enough of the electron module to get sending requests to work module.exports = { app: { - getPath: (/** @type {string} */ name) => name === 'temp' ? require('os').tmpdir() : require('path').join(require('os').tmpdir(), 'insomnia-send-request'), + getPath: (/** @type {string} */ name) => + name === 'temp' ? require('os').tmpdir() : require('path').join(require('os').tmpdir(), 'insomnia-send-request'), }, ipcMain: { on: () => { diff --git a/packages/insomnia/setup-vitest.ts b/packages/insomnia/setup-vitest.ts index feb70c481d..a648df2d06 100644 --- a/packages/insomnia/setup-vitest.ts +++ b/packages/insomnia/setup-vitest.ts @@ -5,7 +5,7 @@ import { electronMock } from './src/__mocks__/electron'; import { database as db } from './src/common/database'; import * as models from './src/models'; import { v4Mock } from './src/models/__mocks__/uuid'; -await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); +await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); vi.mock('electron', () => ({ default: electronMock })); vi.mock('uuid', () => ({ diff --git a/packages/insomnia/src/__mocks__/@getinsomnia/node-libcurl.ts b/packages/insomnia/src/__mocks__/@getinsomnia/node-libcurl.ts index 783f2db9a3..f317346584 100644 --- a/packages/insomnia/src/__mocks__/@getinsomnia/node-libcurl.ts +++ b/packages/insomnia/src/__mocks__/@getinsomnia/node-libcurl.ts @@ -155,12 +155,7 @@ class Curl extends EventEmitter { 'end', 'NOT_USED', 'NOT_USED', - [ - 'HTTP/1.1 200 OK', - `Content-Length: ${data.length}`, - 'Content-Type: application/json', - '', - ].join('\n'), + ['HTTP/1.1 200 OK', `Content-Length: ${data.length}`, 'Content-Type: application/json', ''].join('\n'), ); }); }); diff --git a/packages/insomnia/src/__mocks__/@grpc/grpc-js.ts b/packages/insomnia/src/__mocks__/@grpc/grpc-js.ts index 6188ef92a9..2308fa14d0 100644 --- a/packages/insomnia/src/__mocks__/@grpc/grpc-js.ts +++ b/packages/insomnia/src/__mocks__/@grpc/grpc-js.ts @@ -82,7 +82,6 @@ class MockGrpcClient { makeMockCall(); return getMockCall(); } - } export function makeGenericClientConstructor() { diff --git a/packages/insomnia/src/__tests__/install.test.ts b/packages/insomnia/src/__tests__/install.test.ts index bd467dba24..4575a285d4 100644 --- a/packages/insomnia/src/__tests__/install.test.ts +++ b/packages/insomnia/src/__tests__/install.test.ts @@ -5,7 +5,7 @@ import { containsOnlyDeprecationWarnings, isDeprecatedDependencies } from '../ma describe('install.js', () => { describe('containsOnlyDeprecationWarning', () => { it('should return true when all lines in stderr are deprecation warnings', () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const stderr = // Warning #1 'warning insomnia-plugin-xxx-yyy > xyz > xyz > xyz > xyz > xyz: ' + 'xyz is no longer maintained and not recommended for usage due to the number of issues. ' + @@ -21,7 +21,7 @@ describe('install.js', () => { }); it('should return false when stderr contains a deprecation warning and an error', () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const stderr = // Warning #1 'warning insomnia-plugin-xxx-yyy > xyz > xyz > xyz > xyz > xyz: ' + 'xyz is no longer maintained and not recommended for usage due to the number of issues. ' + diff --git a/packages/insomnia/src/account/__tests__/crypt.test.ts b/packages/insomnia/src/account/__tests__/crypt.test.ts index 0717bb3e3a..642a5f5efe 100644 --- a/packages/insomnia/src/account/__tests__/crypt.test.ts +++ b/packages/insomnia/src/account/__tests__/crypt.test.ts @@ -3,8 +3,8 @@ import { describe, expect, it } from 'vitest'; import * as crypt from '../crypt'; /** -* @vitest-environment jsdom -*/ + * @vitest-environment jsdom + */ describe('crypt', () => { describe('deriveKey()', () => { diff --git a/packages/insomnia/src/account/crypt.ts b/packages/insomnia/src/account/crypt.ts index 2c6b3ff27c..bc9e3f1d04 100644 --- a/packages/insomnia/src/account/crypt.ts +++ b/packages/insomnia/src/account/crypt.ts @@ -56,8 +56,16 @@ export function encryptRSAWithJWK(publicKeyJWK: JsonWebKey, plaintext: string) { } export function decryptRSAWithJWK(privateJWK: JsonWebKey, encryptedBlob: string) { - if (!privateJWK.n || !privateJWK.e || !privateJWK.d || !privateJWK.p || - !privateJWK.q || !privateJWK.dp || !privateJWK.dq || !privateJWK.qi) { + if ( + !privateJWK.n || + !privateJWK.e || + !privateJWK.d || + !privateJWK.p || + !privateJWK.q || + !privateJWK.dp || + !privateJWK.dq || + !privateJWK.qi + ) { throw new Error('Private key is missing parameters'); } @@ -171,7 +179,6 @@ export function decryptAES(jwkOrKey: string | JsonWebKey, encryptedResult: AESMe return decodeURIComponent(decipher.output.toString()); } throw new Error('Failed to decrypt data'); - } /** @@ -202,7 +209,6 @@ export function decryptAESToBuffer(jwkOrKey: string | JsonWebKey, encryptedResul return Buffer.from(forge.util.bytesToHex(decipher.output), 'hex'); } throw new Error('Failed to decrypt data'); - } /** @@ -234,7 +240,6 @@ export async function generateAES256Key() { key_ops: ['encrypt', 'decrypt'], k: _hexToB64Url(key), }; - } /** @@ -296,7 +301,6 @@ export async function generateKeyPairJWK() { privateKey, publicKey, }; - } // ~~~~~~~~~~~~~~~~ // @@ -381,5 +385,4 @@ async function _pbkdf2Passphrase(passphrase: string, salt: string) { forge.md.sha256.create(), ); return forge.util.bytesToHex(derivedKeyRaw); - } diff --git a/packages/insomnia/src/account/session.ts b/packages/insomnia/src/account/session.ts index 1e821e5081..e0a2280396 100644 --- a/packages/insomnia/src/account/session.ts +++ b/packages/insomnia/src/account/session.ts @@ -46,15 +46,7 @@ export function onLoginLogout(loginCallback: LoginCallback) { /** Creates a session from a sessionId and derived symmetric key. */ export async function absorbKey(sessionId: string, key: string) { // Get and store some extra info (salts and keys) - const { - publicKey, - encPrivateKey, - encSymmetricKey, - email, - accountId, - firstName, - lastName, - } = await _whoami(sessionId); + const { publicKey, encPrivateKey, encSymmetricKey, email, accountId, firstName, lastName } = await _whoami(sessionId); const symmetricKeyStr = crypt.decryptAES(key, JSON.parse(encSymmetricKey)); // Store the information for later @@ -170,7 +162,7 @@ async function _whoami(sessionId: string | null = null): Promise const response = await insomniaFetch({ method: 'GET', path: '/auth/whoami', - sessionId: sessionId || await getCurrentSessionId(), + sessionId: sessionId || (await getCurrentSessionId()), }); if (typeof response === 'string') { throw new Error('Unexpected plaintext response: ' + response); diff --git a/packages/insomnia/src/common/__tests__/constants.test.ts b/packages/insomnia/src/common/__tests__/constants.test.ts index aba0c0010e..97f9a1b938 100644 --- a/packages/insomnia/src/common/__tests__/constants.test.ts +++ b/packages/insomnia/src/common/__tests__/constants.test.ts @@ -91,19 +91,25 @@ describe('getContentTypeName', () => { describe('getMockSeviceBinUrl', () => { it('should return correct mock url', () => { - expect(getMockServiceBinURL({ - useInsomniaCloud: true, - _id: 'mock_617eac05d9a94e38a1187f9b4400039b', - url: '', - } as MockServer, '/my-route')).toBe( - 'https://mock-617eac05d9a94e38a1187f9b4400039b.mock.insomnia.rest/my-route' - ); - expect(getMockServiceBinURL({ - useInsomniaCloud: false, - _id: 'mock_617eac05d9a94e38a1187f9b4400039b', - url: 'http://localhost:8080', - } as MockServer, '/my-route')).toBe( - 'http://localhost:8080/bin/mock_617eac05d9a94e38a1187f9b4400039b/my-route' - ); + expect( + getMockServiceBinURL( + { + useInsomniaCloud: true, + _id: 'mock_617eac05d9a94e38a1187f9b4400039b', + url: '', + } as MockServer, + '/my-route', + ), + ).toBe('https://mock-617eac05d9a94e38a1187f9b4400039b.mock.insomnia.rest/my-route'); + expect( + getMockServiceBinURL( + { + useInsomniaCloud: false, + _id: 'mock_617eac05d9a94e38a1187f9b4400039b', + url: 'http://localhost:8080', + } as MockServer, + '/my-route', + ), + ).toBe('http://localhost:8080/bin/mock_617eac05d9a94e38a1187f9b4400039b/my-route'); }); }); diff --git a/packages/insomnia/src/common/__tests__/cookies.test.ts b/packages/insomnia/src/common/__tests__/cookies.test.ts index d883fda185..a61121fe29 100644 --- a/packages/insomnia/src/common/__tests__/cookies.test.ts +++ b/packages/insomnia/src/common/__tests__/cookies.test.ts @@ -1,4 +1,4 @@ -import type { Cookie} from 'tough-cookie'; +import type { Cookie } from 'tough-cookie'; import { CookieJar } from 'tough-cookie'; import { describe, expect, it, vi } from 'vitest'; @@ -25,7 +25,7 @@ describe('jarFromCookies()', async () => { }); it('handles malformed JSON', () => { - vi.spyOn(console, 'log').mockImplementationOnce(() => { }); + vi.spyOn(console, 'log').mockImplementationOnce(() => {}); // @ts-expect-error this test is verifying that an invalid input is handled appropriately const jar = jarFromCookies('not a jar'); expect(jar.constructor.name).toBe('CookieJar'); @@ -61,7 +61,7 @@ describe('cookiesFromJar()', () => { it('handles bad jar', async () => { const jar = CookieJar.fromJSON({ cookies: [] }); - vi.spyOn(console, 'warn').mockImplementationOnce(() => { }); + vi.spyOn(console, 'warn').mockImplementationOnce(() => {}); // MemoryStore never actually throws errors, so lets mock the function to force it to this time. // @ts-expect-error intentionally invalid value jar.store.getAllCookies = cb => cb(new Error('Dummy Error')); diff --git a/packages/insomnia/src/common/__tests__/database.test.ts b/packages/insomnia/src/common/__tests__/database.test.ts index 30533475ea..23a3cdd363 100644 --- a/packages/insomnia/src/common/__tests__/database.test.ts +++ b/packages/insomnia/src/common/__tests__/database.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { BaseModel } from '../../models'; import * as models from '../../models'; -import type { ChangeBufferEvent} from '../database'; +import type { ChangeBufferEvent } from '../database'; import { _repairDatabase, database as db } from '../database'; describe('init()', () => { @@ -19,7 +19,7 @@ describe('init()', () => { describe('onChange()', () => { beforeEach(async () => { - await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); + await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); }); it('handles change listeners', async () => { const doc = { @@ -38,10 +38,7 @@ describe('onChange()', () => { const updatedDoc = await models.request.update(newDoc, { name: 'bar', }); - expect(changesSeen).toEqual([ - [['insert', newDoc, false, []]], - [['update', updatedDoc, false, [{ name: 'bar' }]]], - ]); + expect(changesSeen).toEqual([[['insert', newDoc, false, []]], [['update', updatedDoc, false, [{ name: 'bar' }]]]]); db.offChange(callback); await models.request.create(doc); expect(changesSeen.length).toBe(2); @@ -49,7 +46,6 @@ describe('onChange()', () => { }); describe('bufferChanges()', () => { - it('properly buffers changes', async () => { const doc = { type: models.request.type, @@ -142,7 +138,6 @@ describe('bufferChanges()', () => { }); describe('bufferChangesIndefinitely()', () => { - it('should not auto flush', async () => { const doc = { type: models.request.type, @@ -176,7 +171,6 @@ describe('bufferChangesIndefinitely()', () => { }); describe('requestCreate()', () => { - it('creates a valid request', async () => { const now = Date.now(); const patch = { @@ -212,7 +206,7 @@ describe('requestCreate()', () => { describe('_repairDatabase()', async () => { beforeEach(async () => { - await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); + await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); }); it('fixes duplicate environments', async () => { @@ -597,7 +591,6 @@ describe('_repairDatabase()', async () => { }); describe('duplicate()', () => { - afterEach(() => vi.restoreAllMocks()); it('should overwrite appropriate fields on the parent when duplicating', async () => { @@ -649,7 +642,6 @@ describe('docCreate()', () => { }); describe('withAncestors()', () => { - it('should return itself and all parents but exclude siblings', async () => { const spc = await models.project.create(); const wrk = await models.workspace.create({ @@ -680,16 +672,11 @@ describe('withAncestors()', () => { await expect(db.withAncestors(grpReq)).resolves.toStrictEqual([grpReq, grp, wrk, spc]); await expect(db.withAncestors(grpGrpcReq)).resolves.toStrictEqual([grpGrpcReq, grp, wrk, spc]); // Group child searching for ancestors with filters - await expect(db.withAncestors(grpGrpcReq, [models.requestGroup.type])).resolves.toStrictEqual([ - grpGrpcReq, - grp, - ]); + await expect(db.withAncestors(grpGrpcReq, [models.requestGroup.type])).resolves.toStrictEqual([grpGrpcReq, grp]); await expect( db.withAncestors(grpGrpcReq, [models.requestGroup.type, models.workspace.type]), ).resolves.toStrictEqual([grpGrpcReq, grp, wrk]); // Group child searching for ancestors but excluding groups will not find the workspace - await expect(db.withAncestors(grpGrpcReq, [models.workspace.type])).resolves.toStrictEqual([ - grpGrpcReq, - ]); + await expect(db.withAncestors(grpGrpcReq, [models.workspace.type])).resolves.toStrictEqual([grpGrpcReq]); }); }); diff --git a/packages/insomnia/src/common/__tests__/electron-storage.test.ts b/packages/insomnia/src/common/__tests__/electron-storage.test.ts index 88aebb3275..1108dd37b3 100644 --- a/packages/insomnia/src/common/__tests__/electron-storage.test.ts +++ b/packages/insomnia/src/common/__tests__/electron-storage.test.ts @@ -44,7 +44,7 @@ describe('Test electron storage()', () => { }); it('does handles malformed files', () => { - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { }); + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); const basePath = `/tmp/insomnia-electronstorage-${Math.random()}`; const electronStorage = new ElectronStorage(basePath); @@ -61,7 +61,7 @@ describe('Test electron storage()', () => { }); it('does handles failing to write file', () => { - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { }); + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); const basePath = `/tmp/insomnia-electronstorage-${Math.random()}`; const electronStorage = new ElectronStorage(basePath); fs.rmdirSync(basePath); diff --git a/packages/insomnia/src/common/__tests__/export.test.ts b/packages/insomnia/src/common/__tests__/export.test.ts index 7c0a54fe85..4abd599c95 100644 --- a/packages/insomnia/src/common/__tests__/export.test.ts +++ b/packages/insomnia/src/common/__tests__/export.test.ts @@ -107,7 +107,7 @@ describe('exportWorkspacesHAR() and exportRequestsHAR()', () => { }); it('exports all workspaces as an HTTP Archive', async () => { - await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); + await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); const wrk1 = await models.workspace.create({ _id: 'wrk_1', diff --git a/packages/insomnia/src/common/__tests__/har.test.ts b/packages/insomnia/src/common/__tests__/har.test.ts index 2267ba69d2..cefeb8e679 100644 --- a/packages/insomnia/src/common/__tests__/har.test.ts +++ b/packages/insomnia/src/common/__tests__/har.test.ts @@ -12,7 +12,7 @@ import { getRenderedRequestAndContext } from '../render'; describe('export', () => { beforeEach(async () => { - await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); + await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); await models.project.all(); await models.settings.getOrCreate(); }); diff --git a/packages/insomnia/src/common/__tests__/import.test.ts b/packages/insomnia/src/common/__tests__/import.test.ts index 0b3da88af2..b1444bb555 100644 --- a/packages/insomnia/src/common/__tests__/import.test.ts +++ b/packages/insomnia/src/common/__tests__/import.test.ts @@ -10,12 +10,9 @@ import * as importUtil from '../import'; */ describe('isApiSpecImport()', () => { - it.each(['swagger2', 'openapi3'])( - 'should return true if spec id is %o', - (id: string) => { - expect(importUtil.isApiSpecImport({ id })).toBe(true); - } - ); + it.each(['swagger2', 'openapi3'])('should return true if spec id is %o', (id: string) => { + expect(importUtil.isApiSpecImport({ id })).toBe(true); + }); it('should return false if spec id is not valid', () => { const id = 'invalid-id'; @@ -51,16 +48,14 @@ describe('importRaw()', () => { }); const workspacesCount = await workspace.count(); - const projectWorkspaces = await workspace.findByParentId( - projectToImportTo._id - ); + const projectWorkspaces = await workspace.findByParentId(projectToImportTo._id); const curlRequests = await request.findByParentId(projectWorkspaces[0]._id); expect(workspacesCount).toBe(1); expect(curlRequests[0]).toMatchObject({ body: { - 'text': '{\"email_id\": \"tem_123\"}', + text: '{\"email_id\": \"tem_123\"}', }, }); }); @@ -84,7 +79,7 @@ describe('importRaw()', () => { expect(curlRequests[0]).toMatchObject({ body: { - 'text': '{\"email_id\": \"tem_123\"}', + text: '{\"email_id\": \"tem_123\"}', }, }); }); @@ -102,9 +97,7 @@ describe('importRaw()', () => { projectId: projectToImportTo._id, }); - const projectWorkspaces = await workspace.findByParentId( - projectToImportTo._id - ); + const projectWorkspaces = await workspace.findByParentId(projectToImportTo._id); const requestGroups = await requestGroup.findByParentId(projectWorkspaces[0]._id); const requests = await request.findByParentId(requestGroups[0]._id); @@ -146,5 +139,4 @@ describe('importRaw()', () => { expect(scanResult[0].type?.id).toBe('openapi3'); expect(scanResult[0].errors.length).toBe(0); }); - }); diff --git a/packages/insomnia/src/common/__tests__/render.test.ts b/packages/insomnia/src/common/__tests__/render.test.ts index b99742cb6a..6f49e38d1b 100644 --- a/packages/insomnia/src/common/__tests__/render.test.ts +++ b/packages/insomnia/src/common/__tests__/render.test.ts @@ -97,9 +97,7 @@ describe('render tests', () => { }); it('rendered recursive should not infinite loop', async () => { - const ancestors = [ - reqGroupBuilder.environment({ recursive: '{{ recursive }}/hello' }).build(), - ]; + const ancestors = [reqGroupBuilder.environment({ recursive: '{{ recursive }}/hello' }).build()]; const context = await renderUtils.buildRenderContext({ ancestors }); // This is longer than 3 because it multiplies every time (1 -> 2 -> 4 -> 8) @@ -109,25 +107,31 @@ describe('render tests', () => { }); it('does not recursive render if itself is not used in var', async () => { - const root = envBuilder.data({ - proto: 'http', - domain: 'base.com', - url: '{{ proto }}://{{ domain }}', - }).build(); + const root = envBuilder + .data({ + proto: 'http', + domain: 'base.com', + url: '{{ proto }}://{{ domain }}', + }) + .build(); - const sub = envBuilder.data({ - proto: 'https', - domain: 'sub.com', - port: 8000, - url: '{{ proto }}://{{ domain }}:{{ port }}', - }).build(); + const sub = envBuilder + .data({ + proto: 'https', + domain: 'sub.com', + port: 8000, + url: '{{ proto }}://{{ domain }}:{{ port }}', + }) + .build(); const ancestors = [ - reqGroupBuilder.environment({ - proto: 'https', - domain: 'folder.com', - port: 7000, - }).build(), + reqGroupBuilder + .environment({ + proto: 'https', + domain: 'folder.com', + port: 7000, + }) + .build(), ]; const context = await renderUtils.buildRenderContext({ ancestors, rootEnvironment: root, subEnvironment: sub }); expect(context).toEqual({ @@ -142,10 +146,12 @@ describe('render tests', () => { const root = envBuilder.data({ url: 'insomnia.rest' }).build(); const sub = envBuilder.data({ url: '{{ url }}/sub' }).build(); const ancestors = [ - reqGroupBuilder.environment({ - url: '{{ url }}/{{ name }}', - name: 'folder', - }).build(), + reqGroupBuilder + .environment({ + url: '{{ url }}/{{ name }}', + name: 'folder', + }) + .build(), ]; const context = await renderUtils.buildRenderContext({ ancestors, rootEnvironment: root, subEnvironment: sub }); @@ -157,13 +163,15 @@ describe('render tests', () => { it('render up to 3 recursion levels', async () => { const ancestors = [ - reqGroupBuilder.environment({ - d: '/d', - c: '/c{{ d }}', - b: '/b{{ c }}', - a: '/a{{ b }}', - test: 'http://insomnia.rest{{ a }}', - }).build(), + reqGroupBuilder + .environment({ + d: '/d', + c: '/c{{ d }}', + b: '/b{{ c }}', + a: '/a{{ b }}', + test: 'http://insomnia.rest{{ a }}', + }) + .build(), ]; const context = await renderUtils.buildRenderContext({ ancestors }); expect(context).toEqual({ @@ -177,10 +185,12 @@ describe('render tests', () => { it('rendered sibling environment variables', async () => { const ancestors = [ - reqGroupBuilder.environment({ - sibling: 'sibling', - test: '{{ sibling }}/hello', - }).build(), + reqGroupBuilder + .environment({ + sibling: 'sibling', + test: '{{ sibling }}/hello', + }) + .build(), ]; const context = await renderUtils.buildRenderContext({ ancestors }); expect(context).toEqual({ @@ -257,12 +267,8 @@ describe('render tests', () => { .build(), ]; const context = await renderUtils.buildRenderContext({ ancestors }); - expect(await renderUtils.render('{{ urls.admin }}/foo', context)).toBe( - 'https://parent.com/admin/foo', - ); - expect(await renderUtils.render('{{ urls.test }}/foo', context)).toBe( - 'https://parent.com/test/foo', - ); + expect(await renderUtils.render('{{ urls.admin }}/foo', context)).toBe('https://parent.com/admin/foo'); + expect(await renderUtils.render('{{ urls.test }}/foo', context)).toBe('https://parent.com/test/foo'); }); it('renders child environment variables', async () => { @@ -289,10 +295,7 @@ describe('render tests', () => { it('works with object arrays', async () => { const ancestors = [ - reqGroupBuilder - .name('Parent') - .environment({}) - .build(), + reqGroupBuilder.name('Parent').environment({}).build(), reqGroupBuilder .name('Grandparent') .environment({ @@ -374,7 +377,8 @@ describe('render tests', () => { parentA: 'pa', parentB: 'pb', }, - }).build(), + }) + .build(), reqGroupBuilder .name('Grandparent') .environment({ @@ -384,7 +388,8 @@ describe('render tests', () => { grandParentA: 'gpa', grandParentB: 'gpb', }, - }).build(), + }) + .build(), ]; const context = await renderUtils.buildRenderContext({ ancestors }); expect(context).toEqual({ @@ -416,19 +421,21 @@ describe('render tests', () => { }) .build(), ]; - const subEnvironment = envBuilder.data({ - winner: 'sub', - sub: true, - base_url: 'https://insomnia.rest', - }).build(); - const rootEnvironment = envBuilder.data({ - winner: 'root', - root: true, - base_url: 'ignore this', - }).build(); - const context = await renderUtils.buildRenderContext( - { ancestors, rootEnvironment, subEnvironment }, - ); + const subEnvironment = envBuilder + .data({ + winner: 'sub', + sub: true, + base_url: 'https://insomnia.rest', + }) + .build(); + const rootEnvironment = envBuilder + .data({ + winner: 'root', + root: true, + base_url: 'ignore this', + }) + .build(); + const context = await renderUtils.buildRenderContext({ ancestors, rootEnvironment, subEnvironment }); expect(context).toEqual({ base_url: 'https://insomnia.rest', url: 'https://insomnia.rest/resource', @@ -568,12 +575,7 @@ describe('render tests', () => { const context = { foo: 'bar', }; - const resultOnlyVars = await renderUtils.render( - template, - context, - null, - 'keep', - ); + const resultOnlyVars = await renderUtils.render(template, context, null, 'keep'); expect(resultOnlyVars).toBe('{{ foo }} {% invalid "hi" %}'); try { @@ -695,7 +697,11 @@ describe('render tests', () => { text: '{ "prop": "{{ foo }}" }', }, }); - const request = await renderUtils.getRenderedGrpcRequest({ request: grpcRequest, environmentId: env._id, skipBody: true }); + const request = await renderUtils.getRenderedGrpcRequest({ + request: grpcRequest, + environmentId: env._id, + skipBody: true, + }); expect(request).toEqual( expect.objectContaining({ name: 'hi bar', diff --git a/packages/insomnia/src/common/__tests__/sorting.test.ts b/packages/insomnia/src/common/__tests__/sorting.test.ts index 179f5c8c5e..c5631364ca 100644 --- a/packages/insomnia/src/common/__tests__/sorting.test.ts +++ b/packages/insomnia/src/common/__tests__/sorting.test.ts @@ -32,7 +32,8 @@ describe('Sorting methods', () => { { _id: '', metaSortKey: -800 }, { _id: '', metaSortKey: -799 }, { _id: '', metaSortKey: -1000 }, - { _id: '', metaSortKey: -999 }]; + { _id: '', metaSortKey: -999 }, + ]; const sorted = unsorted.sort(sortMethodMap['type-manual']); expect(sorted).toEqual([ { _id: '', metaSortKey: -1000 }, diff --git a/packages/insomnia/src/common/api-specs.ts b/packages/insomnia/src/common/api-specs.ts index 5471c04b1e..f72ab72e54 100644 --- a/packages/insomnia/src/common/api-specs.ts +++ b/packages/insomnia/src/common/api-specs.ts @@ -7,9 +7,7 @@ export interface ParsedApiSpec { formatVersion: string | null; } -export function parseApiSpec( - rawDocument: string, -) { +export function parseApiSpec(rawDocument: string) { const result: ParsedApiSpec = { contents: null, rawContents: rawDocument, @@ -41,10 +39,7 @@ export function parseApiSpec( return result; } -export function resolveComponentSchemaRefs( - spec: ParsedApiSpec, - methodInfo: Record, -) { +export function resolveComponentSchemaRefs(spec: ParsedApiSpec, methodInfo: Record) { const schemas = spec.contents?.components?.schemas; if (!schemas) { return; diff --git a/packages/insomnia/src/common/common-headers.ts b/packages/insomnia/src/common/common-headers.ts index 67ee4745ed..7789b25f7e 100644 --- a/packages/insomnia/src/common/common-headers.ts +++ b/packages/insomnia/src/common/common-headers.ts @@ -1,4 +1,3 @@ - import allCharsets from '../datasets/charsets'; import allMimeTypes from '../datasets/content-types'; import allEncodings from '../datasets/encodings'; diff --git a/packages/insomnia/src/common/constants.ts b/packages/insomnia/src/common/constants.ts index 13537c18c0..6d2610da02 100644 --- a/packages/insomnia/src/common/constants.ts +++ b/packages/insomnia/src/common/constants.ts @@ -33,15 +33,17 @@ export const isLinux = () => getAppPlatform() === 'linux'; export const isWindows = () => getAppPlatform() === 'win32'; export const getAppEnvironment = () => process.env.INSOMNIA_ENV || 'production'; export const isDevelopment = () => getAppEnvironment() === 'development'; -export const getSegmentWriteKey = () => appConfig.segmentWriteKeys[(isDevelopment() || env.PLAYWRIGHT) ? 'development' : 'production']; +export const getSegmentWriteKey = () => + appConfig.segmentWriteKeys[isDevelopment() || env.PLAYWRIGHT ? 'development' : 'production']; export const getSentryDsn = () => appConfig.sentryDsn; export const getAppBuildDate = () => new Date(process.env.BUILD_DATE ?? '').toLocaleDateString(); -export const getBrowserUserAgent = () => encodeURIComponent( - String(window.navigator.userAgent) - .replace(new RegExp(`${getAppId()}\\/\\d+\\.\\d+\\.\\d+ `), '') - .replace(/Electron\/\d+\.\d+\.\d+ /, ''), -).replace('%2C', ','); +export const getBrowserUserAgent = () => + encodeURIComponent( + String(window.navigator.userAgent) + .replace(new RegExp(`${getAppId()}\\/\\d+\\.\\d+\\.\\d+ `), '') + .replace(/Electron\/\d+\.\d+\.\d+ /, ''), + ).replace('%2C', ','); export function updatesSupported() { // Updates are not supported on Linux @@ -67,7 +69,8 @@ export const CDN_INVALIDATION_TTL = 10_000; // 10 seconds export const STATUS_CODE_PLUGIN_ERROR = -222; export const LARGE_RESPONSE_MB = 5; export const HUGE_RESPONSE_MB = 100; -export const FLEXIBLE_URL_REGEX = /^(http|https):\/\/[\wàâäèéêëîïôóœùûüÿçÀÂÄÈÉÊËÎÏÔŒÙÛÜŸÇ\-_.]+[/\wàâäèéêëîïôóœùûüÿçÀÂÄÈÉÊËÎÏÔŒÙÛÜŸÇ.\-+=:\][@%^*&!#?;$~'(),]*/; +export const FLEXIBLE_URL_REGEX = + /^(http|https):\/\/[\wàâäèéêëîïôóœùûüÿçÀÂÄÈÉÊËÎÏÔŒÙÛÜŸÇ\-_.]+[/\wàâäèéêëîïôóœùûüÿçÀÂÄÈÉÊËÎÏÔŒÙÛÜŸÇ.\-+=:\][@%^*&!#?;$~'(),]*/; export const CHECK_FOR_UPDATES_INTERVAL = 1000 * 60 * 60 * 24; export const ACCEPTED_NODE_CA_FILE_EXTS = ['.pem', '.crt', '.cer', '.p12']; @@ -134,7 +137,6 @@ export const getMockServiceBinURL = (mockServer: MockServer, path: string) => { const url = new URL(baseUrl); url.host = mockServer._id.replace('_', '-') + '.' + url.host; return url.origin + path; - }; export const getAIServiceURL = () => env.INSOMNIA_AI_URL || 'https://ai-helper.insomnia.rest'; @@ -163,11 +165,7 @@ export const MAX_EDITOR_FONT_SIZE = 24; export const DEFAULT_SIDEBAR_SIZE = 25; // Activities -export type GlobalActivity = - | 'spec' - | 'debug' - | 'unittest' - | 'home'; +export type GlobalActivity = 'spec' | 'debug' | 'unittest' | 'home'; export const ACTIVITY_SPEC: GlobalActivity = 'spec'; export const ACTIVITY_DEBUG: GlobalActivity = 'debug'; export const ACTIVITY_UNIT_TEST: GlobalActivity = 'unittest'; @@ -348,12 +346,7 @@ export const sortOrderName: Record = { [SORT_TYPE_ASC]: 'Requests First', }; -export type DashboardSortOrder = - | 'name-asc' - | 'name-desc' - | 'created-asc' - | 'created-desc' - | 'modified-desc'; +export type DashboardSortOrder = 'name-asc' | 'name-desc' | 'created-asc' | 'created-desc' | 'modified-desc'; export const DASHBOARD_SORT_ORDERS: DashboardSortOrder[] = [ SORT_MODIFIED_DESC, @@ -378,7 +371,6 @@ export function getPreviewModeName(previewMode: PreviewMode, useLong = false) { return useLong ? previewModeMap[previewMode][1] : previewModeMap[previewMode][0]; } return ''; - } export function getMimeTypeFromContentType(contentType: string) { // Check if the Content-Type header is provided @@ -412,7 +404,6 @@ export function getAuthTypeName(authType?: string, useLong = false) { return useLong ? authTypesMap[authType][1] : authTypesMap[authType][0]; } return 'Auth'; - } export function getContentTypeFromHeaders(headers: any[], defaultValue: string | null = null) { @@ -427,8 +418,7 @@ export function getContentTypeFromHeaders(headers: any[], defaultValue: string | // Sourced from https://developer.mozilla.org/en-US/docs/Web/HTTP/Status export const RESPONSE_CODE_DESCRIPTIONS: Record = { // Special - [STATUS_CODE_PLUGIN_ERROR]: - 'An Insomnia plugin threw an error which prevented the request from sending', + [STATUS_CODE_PLUGIN_ERROR]: 'An Insomnia plugin threw an error which prevented the request from sending', // 100s 100: 'This interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished.', 101: 'This code is sent in response to an Upgrade: request header by the client and indicates the protocol the server is switching to.', diff --git a/packages/insomnia/src/common/database.ts b/packages/insomnia/src/common/database.ts index ddbd98f413..a430c460ff 100644 --- a/packages/insomnia/src/common/database.ts +++ b/packages/insomnia/src/common/database.ts @@ -165,11 +165,7 @@ export const database = { }, /** find documents matching query */ - find: async function ( - type: string, - query: Query | string = {}, - sort: Sort = { created: 1 }, - ) { + find: async function (type: string, query: Query | string = {}, sort: Sort = { created: 1 }) { if (db._empty) { return _send('find', ...arguments); } @@ -278,7 +274,6 @@ export const database = { return null; } return database.getWhere(type, { _id: id }); - }, getMostRecentlyModified: async function (type: string, query: Query = {}) { @@ -567,7 +562,6 @@ export const database = { return database.update(doc, fromSync); } return database.insert(doc, fromSync); - }, /** get all ancestors of specified types of a document */ @@ -599,10 +593,7 @@ export const database = { } // Continue searching for children - docsToReturn = [ - ...docsToReturn, - ...foundDocs, - ]; + docsToReturn = [...docsToReturn, ...foundDocs]; return next(foundDocs); } @@ -622,7 +613,11 @@ export const database = { * @param queryTypes - An optional array of document types to query. If not provided, all types are queried. * @returns A promise that resolves to an array of all descendant documents. */ - withDescendants: async function (doc: T | null, stopType: string | null = null, queryTypes: string[] = []): Promise { + withDescendants: async function ( + doc: T | null, + stopType: string | null = null, + queryTypes: string[] = [], + ): Promise { if (db._empty) { return _send('withDescendants', ...arguments); } @@ -648,10 +643,7 @@ export const database = { } for (const more of await Promise.all(promises)) { - foundDocs = [ - ...foundDocs, - ...more, - ]; + foundDocs = [...foundDocs, ...more]; } } @@ -708,7 +700,12 @@ let changeListeners: ChangeListener[] = []; /** push changes into the buffer, so that changeListeners can get change contents when database.flushChanges is called, * this method should be called whenever a document change happens */ -async function notifyOfChange(event: ChangeType, doc: T, fromSync: boolean, patches: Patch[] = []) { +async function notifyOfChange( + event: ChangeType, + doc: T, + fromSync: boolean, + patches: Patch[] = [], +) { const updatedDoc = doc; // TODO: Use object is better than array diff --git a/packages/insomnia/src/common/documentation.ts b/packages/insomnia/src/common/documentation.ts index 9f32ee8f0f..0ae0163740 100644 --- a/packages/insomnia/src/common/documentation.ts +++ b/packages/insomnia/src/common/documentation.ts @@ -19,7 +19,8 @@ export const docsGitAccessToken = { gitlab: 'https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html', bitbucket: 'https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/', bitbucketServer: 'https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html', - azureDevOps: 'https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate', + azureDevOps: + 'https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate', }; export const documentationLinks = { diff --git a/packages/insomnia/src/common/export.tsx b/packages/insomnia/src/common/export.tsx index ffee2a05be..bdb564c186 100644 --- a/packages/insomnia/src/common/export.tsx +++ b/packages/insomnia/src/common/export.tsx @@ -18,37 +18,30 @@ import * as har from './har'; import { getInsomniaV5DataExport } from './insomnia-v5'; import { strings } from './strings'; -const getDocWithDescendants = (includePrivateDocs = false) => async (parentDoc: BaseModel | null) => { - const docs = await db.withDescendants(parentDoc); - return docs.filter( - // Don't include if private, except if we want to - doc => !doc?.isPrivate || includePrivateDocs, - ); -}; +const getDocWithDescendants = + (includePrivateDocs = false) => + async (parentDoc: BaseModel | null) => { + const docs = await db.withDescendants(parentDoc); + return docs.filter( + // Don't include if private, except if we want to + doc => !doc?.isPrivate || includePrivateDocs, + ); + }; -export async function exportWorkspacesHAR( - workspaces: Workspace[], - includePrivateDocs = false, -) { +export async function exportWorkspacesHAR(workspaces: Workspace[], includePrivateDocs = false) { const promises = workspaces.map(getDocWithDescendants(includePrivateDocs)); const docs = (await Promise.all(promises)).flat(); const requests = docs.filter(isRequest); return exportRequestsHAR(requests, includePrivateDocs); } -export async function exportRequestsHAR( - requests: BaseModel[], - includePrivateDocs = false, -) { +export async function exportRequestsHAR(requests: BaseModel[], includePrivateDocs = false) { const workspaces: BaseModel[] = []; const mapRequestIdToWorkspace: Record = {}; const workspaceLookup: Record = {}; for (const request of requests) { - const ancestors: BaseModel[] = await db.withAncestors(request, [ - models.workspace.type, - models.requestGroup.type, - ]); + const ancestors: BaseModel[] = await db.withAncestors(request, [models.workspace.type, models.requestGroup.type]); const workspace = ancestors.find(isWorkspace); mapRequestIdToWorkspace[request._id] = workspace; @@ -101,14 +94,9 @@ export async function exportRequestsHAR( const VALUE_YAML = 'yaml'; const VALUE_HAR = 'har'; -export type SelectedFormat = - | typeof VALUE_HAR - | typeof VALUE_YAML - ; +export type SelectedFormat = typeof VALUE_HAR | typeof VALUE_YAML; -const showSelectExportTypeModal = ({ onDone }: { - onDone: (selectedFormat: SelectedFormat) => Promise; -}) => { +const showSelectExportTypeModal = ({ onDone }: { onDone: (selectedFormat: SelectedFormat) => Promise }) => { const options = [ { name: 'Insomnia v5', @@ -203,7 +191,12 @@ export const exportProjectToFile = (activeProjectName: string, workspacesForActi if (!workspacesForActiveProject.length) { showAlert({ title: 'Cannot export', - message: <>There are no workspaces to export in the {activeProjectName} {strings.project.singular.toLowerCase()}., + message: ( + <> + There are no workspaces to export in the {activeProjectName}{' '} + {strings.project.singular.toLowerCase()}. + + ), }); return; } @@ -234,7 +227,10 @@ export const exportProjectToFile = (activeProjectName: string, workspacesForActi if (!fileName) { return; } - const stringifiedExport = await exportWorkspacesHAR(workspacesForActiveProject, shouldExportPrivateEnvironments); + const stringifiedExport = await exportWorkspacesHAR( + workspacesForActiveProject, + shouldExportPrivateEnvironments, + ); await writeExportedFileToFileSystem(fileName, stringifiedExport); @@ -258,7 +254,10 @@ export const exportProjectToFile = (activeProjectName: string, workspacesForActi for (const workspace of workspacesForActiveProject) { const workspaceName = workspace.name.replace(/ /g, '-'); const fileName = path.join(insomniaProjectExportFolder, `${workspaceName}-${workspace._id}.yaml`); - const stringifiedExport = await getInsomniaV5DataExport({ workspaceId: workspace._id, includePrivateEnvironments: shouldExportPrivateEnvironments }); + const stringifiedExport = await getInsomniaV5DataExport({ + workspaceId: workspace._id, + includePrivateEnvironments: shouldExportPrivateEnvironments, + }); await writeExportedFileToFileSystem(fileName, stringifiedExport); } break; @@ -290,9 +289,15 @@ export const exportMockServerToFile = async (workspace: Workspace) => { } try { - const stringifiedExport = await getInsomniaV5DataExport({ workspaceId: workspace._id, includePrivateEnvironments: false }); + const stringifiedExport = await getInsomniaV5DataExport({ + workspaceId: workspace._id, + includePrivateEnvironments: false, + }); await writeExportedFileToFileSystem(fileName, stringifiedExport); - window.main.trackSegmentEvent({ event: SegmentEvent.dataExport, properties: { type: 'yaml', scope: 'mock-server' } }); + window.main.trackSegmentEvent({ + event: SegmentEvent.dataExport, + properties: { type: 'yaml', scope: 'mock-server' }, + }); } catch (err) { showError({ title: 'Export Failed', @@ -326,9 +331,15 @@ export const exportGlobalEnvironmentToFile = async (workspace: Workspace) => { } try { - const stringifiedExport = await getInsomniaV5DataExport({ workspaceId: workspace._id, includePrivateEnvironments: shouldExportPrivateEnvironments }); + const stringifiedExport = await getInsomniaV5DataExport({ + workspaceId: workspace._id, + includePrivateEnvironments: shouldExportPrivateEnvironments, + }); await writeExportedFileToFileSystem(fileName, stringifiedExport); - window.main.trackSegmentEvent({ event: SegmentEvent.dataExport, properties: { type: 'yaml', scope: 'environment' } }); + window.main.trackSegmentEvent({ + event: SegmentEvent.dataExport, + properties: { type: 'yaml', scope: 'environment' }, + }); } catch (err) { showError({ title: 'Export Failed', @@ -379,7 +390,11 @@ export const exportRequestsToFile = (workspaceId: string, requestIds: string[]) break; case VALUE_YAML: - stringifiedExport = await getInsomniaV5DataExport({ workspaceId, includePrivateEnvironments: shouldExportPrivateEnvironments, requestIds }); + stringifiedExport = await getInsomniaV5DataExport({ + workspaceId, + includePrivateEnvironments: shouldExportPrivateEnvironments, + requestIds, + }); break; default: @@ -395,7 +410,6 @@ export const exportRequestsToFile = (workspaceId: string, requestIds: string[]) }); return; } - }, }); }; @@ -420,11 +434,7 @@ export async function exportWorkspaceData({ } } -export async function exportAllData({ - dirPath, -}: { - dirPath: string; -}): Promise { +export async function exportAllData({ dirPath }: { dirPath: string }): Promise { const workspaces = await database.find(models.workspace.type); const baseEnvironments = await database.find(environment.type, { diff --git a/packages/insomnia/src/common/har.ts b/packages/insomnia/src/common/har.ts index f52aede477..8128a6a38c 100644 --- a/packages/insomnia/src/common/har.ts +++ b/packages/insomnia/src/common/har.ts @@ -152,11 +152,7 @@ export async function exportHarResponse(response: Response | null) { return harResponse; } -export async function exportHarRequest( - requestId: string, - environmentId: string, - addContentLength = false, -) { +export async function exportHarRequest(requestId: string, environmentId: string, addContentLength = false) { const request = await models.request.getById(requestId); if (!request) { @@ -166,17 +162,10 @@ export async function exportHarRequest( return exportHarWithRequest(request, environmentId, addContentLength); } -export async function exportHarWithRequest( - request: Request, - environmentId?: string, - addContentLength = false, -) { +export async function exportHarWithRequest(request: Request, environmentId?: string, addContentLength = false) { try { const renderResult = await getRenderedRequestAndContext({ request, environment: environmentId }); - const renderedRequest = await _applyRequestPluginHooks( - renderResult.request, - renderResult.context, - ); + const renderedRequest = await _applyRequestPluginHooks(renderResult.request, renderResult.context); parseGraphQLReqeustBody(renderedRequest); return exportHarWithRenderedRequest(renderedRequest, addContentLength); } catch (err) { @@ -213,15 +202,11 @@ async function _applyRequestPluginHooks( return newRenderedRequest; } -export async function exportHarWithRenderedRequest( - renderedRequest: RenderedRequest, - addContentLength = false, -) { +export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequest, addContentLength = false) { const url = smartEncodeUrl(renderedRequest.url, renderedRequest.settingEncodeUrl); if (addContentLength) { - const hasContentLengthHeader = - filterHeaders(renderedRequest.headers, 'Content-Length').length > 0; + const hasContentLengthHeader = filterHeaders(renderedRequest.headers, 'Content-Length').length > 0; if (!hasContentLengthHeader) { const name = 'Content-Length'; @@ -277,23 +262,19 @@ function getRequestCookies(renderedRequest: RenderedRequest) { } export function getResponseCookiesFromHeaders(headers: Har.Cookie[]) { - return getSetCookieHeaders(headers) - .reduce((accumulator, harCookie) => { - let cookie: null | undefined | ToughCookie = null; + return getSetCookieHeaders(headers).reduce((accumulator, harCookie) => { + let cookie: null | undefined | ToughCookie = null; - try { - cookie = ToughCookie.parse(harCookie.value || '', { loose: true }); - } catch (error) { } + try { + cookie = ToughCookie.parse(harCookie.value || '', { loose: true }); + } catch (error) {} - if (cookie === null || cookie === undefined) { - return accumulator; - } + if (cookie === null || cookie === undefined) { + return accumulator; + } - return [ - ...accumulator, - mapCookie(cookie), - ]; - }, [] as Har.Cookie[]); + return [...accumulator, mapCookie(cookie)]; + }, [] as Har.Cookie[]); } function getResponseCookies(response: Response) { @@ -403,9 +384,7 @@ function getRequestPostData(renderedRequest: RenderedRequest): Har.PostData | un mimeType: body.mimeType || '', params: body.params.map(({ name, value, fileName, type }) => ({ name, - ...(type === 'file' - ? { fileName } - : { value }), + ...(type === 'file' ? { fileName } : { value }), })), }; } diff --git a/packages/insomnia/src/common/hotkeys.ts b/packages/insomnia/src/common/hotkeys.ts index e7b4ab4f6f..879bf29eee 100644 --- a/packages/insomnia/src/common/hotkeys.ts +++ b/packages/insomnia/src/common/hotkeys.ts @@ -8,34 +8,34 @@ import { strings } from './strings'; * @IMPORTANT Not using dot, because NeDB prohibits field names to contain dots. */ export const keyboardShortcutDescriptions: Record = { - 'workspace_showSettings': `Show ${strings.document.singular} / ${strings.collection.singular} Settings`, - 'request_showSettings': 'Show Request Settings', - 'preferences_showKeyboardShortcuts': 'Show Keyboard Shortcuts', - 'preferences_showGeneral': 'Show App Preferences', - 'request_quickSwitch': 'Quick search', - 'plugin_reload': 'Reload Plugins', - 'showAutocomplete': 'Show Autocomplete', - 'request_send': 'Send Request', - 'request_showOptions': 'Send Request (Options)', - 'environment_showEditor': 'Show Environment Editor', - 'environment_showSwitchMenu': 'Switch Environments', - 'request_toggleHttpMethodMenu': 'Change HTTP Method', - 'request_toggleHistory': 'Show Request History', - 'request_focusUrl': 'Focus URL', - 'request_showGenerateCodeEditor': 'Generate Code', - 'sidebar_focusFilter': 'Filter Sidebar', - 'sidebar_toggle': 'Toggle Sidebar', - 'response_focus': 'Focus Response', - 'showCookiesEditor': 'Edit Cookies', - 'request_createHTTP': 'Create HTTP Request', - 'request_showDelete': 'Delete Request', - 'request_showCreateFolder': 'Create Folder', - 'request_showDuplicate': 'Duplicate Request', - 'request_togglePin': 'Pin/Unpin Request', - 'environment_showVariableSourceAndValue': 'Show variable source and value', - 'beautifyRequestBody': 'Beautify Active Code Editors', - 'graphql_explorer_focus_filter': 'Focus GraphQL Explorer Filter', - 'close_tab': 'Close Tab', + workspace_showSettings: `Show ${strings.document.singular} / ${strings.collection.singular} Settings`, + request_showSettings: 'Show Request Settings', + preferences_showKeyboardShortcuts: 'Show Keyboard Shortcuts', + preferences_showGeneral: 'Show App Preferences', + request_quickSwitch: 'Quick search', + plugin_reload: 'Reload Plugins', + showAutocomplete: 'Show Autocomplete', + request_send: 'Send Request', + request_showOptions: 'Send Request (Options)', + environment_showEditor: 'Show Environment Editor', + environment_showSwitchMenu: 'Switch Environments', + request_toggleHttpMethodMenu: 'Change HTTP Method', + request_toggleHistory: 'Show Request History', + request_focusUrl: 'Focus URL', + request_showGenerateCodeEditor: 'Generate Code', + sidebar_focusFilter: 'Filter Sidebar', + sidebar_toggle: 'Toggle Sidebar', + response_focus: 'Focus Response', + showCookiesEditor: 'Edit Cookies', + request_createHTTP: 'Create HTTP Request', + request_showDelete: 'Delete Request', + request_showCreateFolder: 'Create Folder', + request_showDuplicate: 'Duplicate Request', + request_togglePin: 'Pin/Unpin Request', + environment_showVariableSourceAndValue: 'Show variable source and value', + beautifyRequestBody: 'Beautify Active Code Editors', + graphql_explorer_focus_filter: 'Focus GraphQL Explorer Filter', + close_tab: 'Close Tab', }; /** @@ -191,10 +191,7 @@ export function getPlatformKeyCombinations(bindings: PlatformKeyCombinations): K /** * Determine whether two key combinations are the same by comparing each of their keys. */ -export function areSameKeyCombinations( - keyComb1: KeyCombination, - keyComb2: KeyCombination, -) { +export function areSameKeyCombinations(keyComb1: KeyCombination, keyComb2: KeyCombination) { return ( keyComb1.keyCode === keyComb2.keyCode && Boolean(keyComb1.alt) === Boolean(keyComb2.alt) && @@ -236,7 +233,6 @@ export function isModifierKeyCode(keyCode: number): boolean { keyCode === keyboardKeys.alt.keyCode || keyCode === keyboardKeys.shift.keyCode || keyCode === keyboardKeys.ctrl.keyCode || - // Meta keys. keyCode === keyboardKeys.leftwindowkey.keyCode || keyCode === keyboardKeys.rightwindowkey.keyCode || @@ -253,10 +249,7 @@ export function isModifierKeyCode(keyCode: number): boolean { * @returns the constructed string, if keyCode is null and the characters are joined with " + ", * it will have a dangling "+" as the last character, e.g., "Alt + Ctrl +". */ -export function constructKeyCombinationDisplay( - keyComb: KeyCombination, - mustUsePlus: boolean, -) { +export function constructKeyCombinationDisplay(keyComb: KeyCombination, mustUsePlus: boolean) { const { keyCode } = keyComb; const chars: string[] = []; diff --git a/packages/insomnia/src/common/import-v5-parser.ts b/packages/insomnia/src/common/import-v5-parser.ts index 0d58667865..f66bcc6f76 100644 --- a/packages/insomnia/src/common/import-v5-parser.ts +++ b/packages/insomnia/src/common/import-v5-parser.ts @@ -6,9 +6,7 @@ const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); type Literal = z.infer; type Json = Literal | { [key: string]: Json } | Json[]; -const jsonSchema: z.ZodType = z.lazy(() => - z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]) -); +const jsonSchema: z.ZodType = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])); const MetaSchema = z.object({ id: z.string(), @@ -28,7 +26,10 @@ const CACertificateSchema = z.object({ }); const CookieSchema = z.object({ - id: z.string().optional().default(() => crypto.randomUUID()), + id: z + .string() + .optional() + .default(() => crypto.randomUUID()), key: z.string().optional().default(''), value: z.string().optional().default(''), expires: z.coerce.date().nullable().default(null), @@ -58,15 +59,19 @@ const EnvironmentSchema = z.object({ meta: MetaSchema.extend({ sortKey: z.number().optional(), }).optional(), - subEnvironments: z.array(z.object({ - name: z.string(), - data: jsonSchema.optional(), - dataPropertyOrder: jsonSchema.optional(), - color: z.string().optional().nullable(), - meta: MetaSchema.extend({ - sortKey: z.number().optional(), - }).optional(), - })).optional(), + subEnvironments: z + .array( + z.object({ + name: z.string(), + data: jsonSchema.optional(), + dataPropertyOrder: jsonSchema.optional(), + color: z.string().optional().nullable(), + meta: MetaSchema.extend({ + sortKey: z.number().optional(), + }).optional(), + }), + ) + .optional(), }); export const GRPCRequestSchema = z.object({ @@ -74,15 +79,21 @@ export const GRPCRequestSchema = z.object({ url: z.string().optional().default(''), protoFileId: z.string().optional().nullable(), protoMethodName: z.string().optional(), - body: z.object({ - text: z.string().optional(), - }).optional(), - metadata: z.array(z.object({ - name: z.string().optional().default(''), - value: z.string().optional().default(''), - description: z.string().optional(), - disabled: z.boolean().optional(), - })).optional(), + body: z + .object({ + text: z.string().optional(), + }) + .optional(), + metadata: z + .array( + z.object({ + name: z.string().optional().default(''), + value: z.string().optional().default(''), + description: z.string().optional(), + disabled: z.boolean().optional(), + }), + ) + .optional(), reflectionApi: z.object({ enabled: z.boolean().optional().default(false), url: z.string().optional().default(''), @@ -101,148 +112,188 @@ const MockRouteSchema = z.object({ name: z.string().optional(), mimeType: z.string().optional(), method: z.string().optional(), - headers: z.array(z.object({ - name: z.string(), - value: z.string(), - })).optional(), + headers: z + .array( + z.object({ + name: z.string(), + value: z.string(), + }), + ) + .optional(), meta: MetaSchema.optional(), }); const AuthenticationSchema = z.union([ z.discriminatedUnion('type', [ - z.object({ - type: z.literal('basic'), - useISO88591: z.boolean().default(false), - username: z.string(), - password: z.string(), - disabled: z.boolean().optional(), - }, { - description: 'Basic Authentication', - }), - z.object({ - type: z.literal('apikey'), - key: z.string().optional(), - value: z.string().optional(), - disabled: z.boolean().optional(), - addTo: z.string().optional(), - }, { - description: 'API Key Authentication', - }), - z.object({ - type: z.literal('oauth2'), - disabled: z.boolean().optional(), - grantType: z.enum(['authorization_code', 'client_credentials', 'implicit', 'password', 'refresh_token']), - accessTokenUrl: z.string().optional(), - authorizationUrl: z.string().optional(), - clientId: z.string().optional(), - clientSecret: z.string().optional(), - audience: z.string().optional(), - scope: z.string().optional(), - resource: z.string().optional(), - username: z.string().optional(), - password: z.string().optional(), - redirectUrl: z.string().optional(), - credentialsInBody: z.boolean().optional(), - state: z.string().optional(), - code: z.string().optional(), - accessToken: z.string().optional(), - refreshToken: z.string().optional(), - tokenPrefix: z.string().optional(), - usePkce: z.boolean().optional(), - pkceMethod: z.string().optional(), - responseType: z.enum(['code', 'token', 'none', 'id_token', 'id_token token']).optional(), - origin: z.string().optional(), - }, { - description: 'OAuth 2.0 Authentication', - }), - z.object({ - type: z.literal('hawk'), - id: z.string().optional().default(''), - key: z.string().optional().default(''), - ext: z.string().optional(), - validatePayload: z.boolean().optional(), - algorithm: z.enum(['sha1', 'sha256']), - disabled: z.boolean().optional(), - }, { - description: 'Hawk Authentication', - }), - z.object({ - type: z.literal('oauth1'), - disabled: z.boolean().optional(), - signatureMethod: z.enum(['HMAC-SHA1', 'RSA-SHA1', 'HMAC-SHA256', 'PLAINTEXT']), - consumerKey: z.string().optional(), - tokenKey: z.string().optional(), - tokenSecret: z.string().optional(), - privateKey: z.string().optional(), - version: z.string().optional(), - nonce: z.string().optional(), - timestamp: z.string().optional(), - callback: z.string().optional(), - realm: z.string().optional(), - verifier: z.string().optional(), - includeBodyHash: z.boolean().optional(), - }, { - description: 'OAuth 1.0 Authentication', - }), - z.object({ - type: z.literal('digest'), - disabled: z.boolean().optional(), - username: z.string().optional(), - password: z.string().optional(), - }, { - description: 'Digest Authentication', - }), - z.object({ - type: z.literal('ntlm'), - disabled: z.boolean().optional(), - username: z.string().optional().default(''), - password: z.string().optional().default(''), - }, { - description: 'NTLM Authentication', - }), - z.object({ - type: z.literal('bearer'), - disabled: z.boolean().optional(), - token: z.string().optional(), - prefix: z.string().optional(), - }, { - description: 'Bearer Authentication', - }), - z.object({ - type: z.literal('iam'), - disabled: z.boolean().optional(), - accessKeyId: z.string().optional(), - secretAccessKey: z.string().optional(), - sessionToken: z.string().optional(), - region: z.string().optional(), - service: z.string().optional(), - }, { - description: 'AWS IAM Authentication', - }), - z.object({ - type: z.literal('netrc'), - disabled: z.boolean().optional(), - }, { - description: 'Netrc Authentication', - }), - z.object({ - type: z.literal('asap'), - disabled: z.boolean().optional(), - issuer: z.string().optional(), - subject: z.string().optional(), - audience: z.string().optional(), - addintionalClaims: z.string().optional(), - privateKey: z.string().optional(), - keyId: z.string().optional(), - }, { - description: 'ASAP Authentication', - }), - z.object({ - type: z.literal('none'), - disabled: z.boolean().optional(), - }, { - description: 'No Authentication', - }), + z.object( + { + type: z.literal('basic'), + useISO88591: z.boolean().default(false), + username: z.string(), + password: z.string(), + disabled: z.boolean().optional(), + }, + { + description: 'Basic Authentication', + }, + ), + z.object( + { + type: z.literal('apikey'), + key: z.string().optional(), + value: z.string().optional(), + disabled: z.boolean().optional(), + addTo: z.string().optional(), + }, + { + description: 'API Key Authentication', + }, + ), + z.object( + { + type: z.literal('oauth2'), + disabled: z.boolean().optional(), + grantType: z.enum(['authorization_code', 'client_credentials', 'implicit', 'password', 'refresh_token']), + accessTokenUrl: z.string().optional(), + authorizationUrl: z.string().optional(), + clientId: z.string().optional(), + clientSecret: z.string().optional(), + audience: z.string().optional(), + scope: z.string().optional(), + resource: z.string().optional(), + username: z.string().optional(), + password: z.string().optional(), + redirectUrl: z.string().optional(), + credentialsInBody: z.boolean().optional(), + state: z.string().optional(), + code: z.string().optional(), + accessToken: z.string().optional(), + refreshToken: z.string().optional(), + tokenPrefix: z.string().optional(), + usePkce: z.boolean().optional(), + pkceMethod: z.string().optional(), + responseType: z.enum(['code', 'token', 'none', 'id_token', 'id_token token']).optional(), + origin: z.string().optional(), + }, + { + description: 'OAuth 2.0 Authentication', + }, + ), + z.object( + { + type: z.literal('hawk'), + id: z.string().optional().default(''), + key: z.string().optional().default(''), + ext: z.string().optional(), + validatePayload: z.boolean().optional(), + algorithm: z.enum(['sha1', 'sha256']), + disabled: z.boolean().optional(), + }, + { + description: 'Hawk Authentication', + }, + ), + z.object( + { + type: z.literal('oauth1'), + disabled: z.boolean().optional(), + signatureMethod: z.enum(['HMAC-SHA1', 'RSA-SHA1', 'HMAC-SHA256', 'PLAINTEXT']), + consumerKey: z.string().optional(), + tokenKey: z.string().optional(), + tokenSecret: z.string().optional(), + privateKey: z.string().optional(), + version: z.string().optional(), + nonce: z.string().optional(), + timestamp: z.string().optional(), + callback: z.string().optional(), + realm: z.string().optional(), + verifier: z.string().optional(), + includeBodyHash: z.boolean().optional(), + }, + { + description: 'OAuth 1.0 Authentication', + }, + ), + z.object( + { + type: z.literal('digest'), + disabled: z.boolean().optional(), + username: z.string().optional(), + password: z.string().optional(), + }, + { + description: 'Digest Authentication', + }, + ), + z.object( + { + type: z.literal('ntlm'), + disabled: z.boolean().optional(), + username: z.string().optional().default(''), + password: z.string().optional().default(''), + }, + { + description: 'NTLM Authentication', + }, + ), + z.object( + { + type: z.literal('bearer'), + disabled: z.boolean().optional(), + token: z.string().optional(), + prefix: z.string().optional(), + }, + { + description: 'Bearer Authentication', + }, + ), + z.object( + { + type: z.literal('iam'), + disabled: z.boolean().optional(), + accessKeyId: z.string().optional(), + secretAccessKey: z.string().optional(), + sessionToken: z.string().optional(), + region: z.string().optional(), + service: z.string().optional(), + }, + { + description: 'AWS IAM Authentication', + }, + ), + z.object( + { + type: z.literal('netrc'), + disabled: z.boolean().optional(), + }, + { + description: 'Netrc Authentication', + }, + ), + z.object( + { + type: z.literal('asap'), + disabled: z.boolean().optional(), + issuer: z.string().optional(), + subject: z.string().optional(), + audience: z.string().optional(), + addintionalClaims: z.string().optional(), + privateKey: z.string().optional(), + keyId: z.string().optional(), + }, + { + description: 'ASAP Authentication', + }, + ), + z.object( + { + type: z.literal('none'), + disabled: z.boolean().optional(), + }, + { + description: 'No Authentication', + }, + ), ]), z.object({}), ]); @@ -272,15 +323,19 @@ export const WebSocketRequestSettingsSchema = z.object({ followRedirects: z.enum(['global', 'on', 'off']).optional().default('global'), }); -export const RequestParametersSchema = z.array(z.object({ - name: z.string().optional().default(''), - value: z.string().optional().default(''), -})); +export const RequestParametersSchema = z.array( + z.object({ + name: z.string().optional().default(''), + value: z.string().optional().default(''), + }), +); -export const RequestHeadersSchema = z.array(z.object({ - name: z.string().optional().default(''), - value: z.string().optional().default(''), -})); +export const RequestHeadersSchema = z.array( + z.object({ + name: z.string().optional().default(''), + value: z.string().optional().default(''), + }), +); export const RequestGroupSchema = z.object({ name: z.string().optional().default(''), @@ -299,36 +354,54 @@ export const RequestSchema = z.object({ url: z.string().optional().default(''), name: z.string().optional().default(''), method: z.string(), - body: z.object({ - mimeType: z.string().optional().nullable(), - text: z.string().optional(), - fileName: z.string().optional(), - params: z.array(z.object({ - name: z.string(), - value: z.string().optional().default(''), - description: z.string().optional(), - disabled: z.boolean().optional(), - multiline: z.string().optional(), - id: z.string().optional(), + body: z + .object({ + mimeType: z.string().optional().nullable(), + text: z.string().optional(), fileName: z.string().optional(), - type: z.string().optional(), - })).optional(), - }).optional(), - headers: z.array(z.object({ - name: z.string().optional().default(''), - value: z.string().optional().default(''), - })).optional(), - parameters: z.array(z.object({ - name: z.string().optional().default(''), - value: z.string().optional().default(''), - disabled: z.boolean().optional(), - id: z.string().optional(), - fileName: z.string().optional(), - })).optional(), - pathParameters: z.array(z.object({ - name: z.string().optional().default(''), - value: z.string().optional().default(''), - })).optional(), + params: z + .array( + z.object({ + name: z.string(), + value: z.string().optional().default(''), + description: z.string().optional(), + disabled: z.boolean().optional(), + multiline: z.string().optional(), + id: z.string().optional(), + fileName: z.string().optional(), + type: z.string().optional(), + }), + ) + .optional(), + }) + .optional(), + headers: z + .array( + z.object({ + name: z.string().optional().default(''), + value: z.string().optional().default(''), + }), + ) + .optional(), + parameters: z + .array( + z.object({ + name: z.string().optional().default(''), + value: z.string().optional().default(''), + disabled: z.boolean().optional(), + id: z.string().optional(), + fileName: z.string().optional(), + }), + ) + .optional(), + pathParameters: z + .array( + z.object({ + name: z.string().optional().default(''), + value: z.string().optional().default(''), + }), + ) + .optional(), authentication: AuthenticationSchema.optional(), scripts: ScriptsSchema.optional(), settings: RequestSettingsSchema.optional().default({ @@ -382,23 +455,25 @@ const RequestGroupWithChildrenSchema: z.ZodType = RequestGroupSche pathParameters: z.undefined(), }); -const RequestCollectionSchema = z.union([ - GRPCRequestSchema.extend({ - // These undefined properties are added to differentiate between the different types of children in the union - children: z.undefined(), - method: z.undefined(), - }), - RequestSchema.extend({ - // These undefined properties are added to differentiate between the different types of children in the union - children: z.undefined(), - }), - WebsocketRequestSchema.extend({ - // These undefined properties are added to differentiate between the different types of children in the union - children: z.undefined(), - method: z.undefined(), - }), - RequestGroupWithChildrenSchema, -]).array(); +const RequestCollectionSchema = z + .union([ + GRPCRequestSchema.extend({ + // These undefined properties are added to differentiate between the different types of children in the union + children: z.undefined(), + method: z.undefined(), + }), + RequestSchema.extend({ + // These undefined properties are added to differentiate between the different types of children in the union + children: z.undefined(), + }), + WebsocketRequestSchema.extend({ + // These undefined properties are added to differentiate between the different types of children in the union + children: z.undefined(), + method: z.undefined(), + }), + RequestGroupWithChildrenSchema, + ]) + .array(); const TestSchema = z.object({ name: z.string().optional().default(''), @@ -417,13 +492,16 @@ const TestSuiteSchema = z.object({ tests: z.array(TestSchema).optional(), }); -const SpecSchema = z.union([z.object({ - meta: MetaSchema.optional(), - file: z.string(), -}), z.object({ - meta: MetaSchema.optional(), - contents: jsonSchema.optional(), -})]); +const SpecSchema = z.union([ + z.object({ + meta: MetaSchema.optional(), + file: z.string(), + }), + z.object({ + meta: MetaSchema.optional(), + contents: jsonSchema.optional(), + }), +]); const collectionSchema = z.object({ type: z.literal('collection.insomnia.rest/5.0'), @@ -454,11 +532,13 @@ const mockServerSchema = z.object({ meta: MetaSchema.optional(), name: z.string().optional(), description: z.string().optional(), - server: z.object({ - meta: MetaSchema.optional(), - url: z.string(), - useInsomniaCloud: z.boolean().default(true), - }).optional(), + server: z + .object({ + meta: MetaSchema.optional(), + url: z.string(), + useInsomniaCloud: z.boolean().default(true), + }) + .optional(), routes: z.array(MockRouteSchema).optional(), }); diff --git a/packages/insomnia/src/common/import.ts b/packages/insomnia/src/common/import.ts index 086689b29a..163e71ecb2 100644 --- a/packages/insomnia/src/common/import.ts +++ b/packages/insomnia/src/common/import.ts @@ -12,10 +12,7 @@ import { isRequest, type Request } from '../models/request'; import { isRequestGroup } from '../models/request-group'; import { isUnitTest, type UnitTest } from '../models/unit-test'; import { isUnitTestSuite, type UnitTestSuite } from '../models/unit-test-suite'; -import { - isWebSocketRequest, - type WebSocketRequest, -} from '../models/websocket-request'; +import { isWebSocketRequest, type WebSocketRequest } from '../models/websocket-request'; import { isWorkspace, type Workspace } from '../models/workspace'; import type { CurrentPlan } from '../ui/routes/organization'; import { convert, type InsomniaImporter } from '../utils/importers/convert'; @@ -37,19 +34,20 @@ interface ConvertResult { } const isSubEnvironmentResource = (environment: Environment) => { - return !environment.parentId || environment.parentId.startsWith(models.environment.prefix) || environment.parentId.startsWith('__BASE_ENVIRONMENT_ID__'); + return ( + !environment.parentId || + environment.parentId.startsWith(models.environment.prefix) || + environment.parentId.startsWith('__BASE_ENVIRONMENT_ID__') + ); }; -export const isInsomniaV4Import = ({ id }: Pick) => - id === 'insomnia-4'; +export const isInsomniaV4Import = ({ id }: Pick) => id === 'insomnia-4'; export async function fetchImportContentFromURI({ uri }: { uri: string }) { const url = new URL(uri); if (url.origin === 'https://github.com') { - uri = uri - .replace('https://github.com', 'https://raw.githubusercontent.com') - .replace('blob/', ''); + uri = uri.replace('https://github.com', 'https://raw.githubusercontent.com').replace('blob/', ''); } if (uri.match(/^(http|https):\/\//)) { @@ -67,7 +65,6 @@ export async function fetchImportContentFromURI({ uri }: { uri: string }) { const content = decodeURIComponent(uri); return content; - } export interface ImportFileDetail { @@ -120,91 +117,93 @@ let resourceCacheList: ResourceCacheType[] = []; export async function scanResources(contentList: string[] | ImportFileDetail[]): Promise { resourceCacheList = []; - const results = await Promise.allSettled(contentList.map(async content => { - const contentStr = typeof content === 'string' ? content : content.contentStr; - const oriFileName = typeof content === 'string' ? '' : content.oriFileName; + const results = await Promise.allSettled( + contentList.map(async content => { + const contentStr = typeof content === 'string' ? content : content.contentStr; + const oriFileName = typeof content === 'string' ? '' : content.oriFileName; - let result: ConvertResult | null = null; + let result: ConvertResult | null = null; - try { - const insomnia5Import = importInsomniaV5Data(contentStr); - if (insomnia5Import.length > 0) { - result = { - type: { - id: 'insomnia-5', - name: 'Insomnia v5', - description: 'Insomnia v5', - }, - data: { - // @ts-expect-error -- TSCONVERSION - resources: insomnia5Import, - }, - }; - } else { - result = (await convert(contentStr)) as unknown as ConvertResult; + try { + const insomnia5Import = importInsomniaV5Data(contentStr); + if (insomnia5Import.length > 0) { + result = { + type: { + id: 'insomnia-5', + name: 'Insomnia v5', + description: 'Insomnia v5', + }, + data: { + // @ts-expect-error -- TSCONVERSION + resources: insomnia5Import, + }, + }; + } else { + result = (await convert(contentStr)) as unknown as ConvertResult; + } + } catch (err: unknown) { + if (err instanceof Error) { + return { + oriFileName, + errors: [err.message], + }; + } } - } catch (err: unknown) { - if (err instanceof Error) { + + if (!result) { return { oriFileName, - errors: [err.message], + errors: ['No resources found to import.'], }; } - } - if (!result) { - return { - oriFileName, - errors: ['No resources found to import.'], - }; - } + const { type, data } = result; - const { type, data } = result; + const resources = data.resources + .filter(r => r._type) + .map(r => { + const { _type, ...model } = r; + return { ...model, type: models.MODELS_BY_EXPORT_TYPE[_type].type }; + }); - const resources = data.resources - .filter(r => r._type) - .map(r => { - const { _type, ...model } = r; - return { ...model, type: models.MODELS_BY_EXPORT_TYPE[_type].type }; + resourceCacheList.push({ + resources, + importer: type, + content: contentStr, }); - resourceCacheList.push({ - resources, - importer: type, - content: contentStr, - }); + const requests = resources.filter(isRequest); + const websocketRequests = resources.filter(isWebSocketRequest); + const grpcRequests = resources.filter(isGrpcRequest); + const environments = resources.filter(isEnvironment); + const unitTests = resources.filter(isUnitTest); + const unitTestSuites = resources.filter(isUnitTestSuite); + const apiSpecs = resources.filter(isApiSpec); + const workspaces = resources.filter(isWorkspace); + const cookieJars = resources.filter(isCookieJar); + const mockRoutes = resources.filter(isMockRoute); - const requests = resources.filter(isRequest); - const websocketRequests = resources.filter(isWebSocketRequest); - const grpcRequests = resources.filter(isGrpcRequest); - const environments = resources.filter(isEnvironment); - const unitTests = resources.filter(isUnitTest); - const unitTestSuites = resources.filter(isUnitTestSuite); - const apiSpecs = resources.filter(isApiSpec); - const workspaces = resources.filter(isWorkspace); - const cookieJars = resources.filter(isCookieJar); - const mockRoutes = resources.filter(isMockRoute); - - return { - type, - unitTests, - unitTestSuites, - requests: [...requests, ...websocketRequests, ...grpcRequests], - workspaces, - environments, - apiSpecs, - cookieJars, - mockRoutes, - oriFileName, - errors: [], - }; - })); - return results.map( - retObj => retObj.status === 'fulfilled' + return { + type, + unitTests, + unitTestSuites, + requests: [...requests, ...websocketRequests, ...grpcRequests], + workspaces, + environments, + apiSpecs, + cookieJars, + mockRoutes, + oriFileName, + errors: [], + }; + }), + ); + return results.map(retObj => + retObj.status === 'fulfilled' ? retObj.value : { - errors: [retObj.reason.toString()], - } + errors: [retObj.reason.toString()], + }, ); } @@ -217,15 +216,12 @@ export async function importResourcesToProject({ }) { invariant(resourceCacheList.length > 0, 'No resources to import'); for (const resourceCacheItem of resourceCacheList) { - const { - resources, - importer, - } = resourceCacheItem; + const { resources, importer } = resourceCacheItem; const bufferId = await db.bufferChanges(); // if the resource is postman collection const postmanTopLevelFolder = resources.find( - resource => isRequestGroup(resource) && resource.parentId === '__WORKSPACE_ID__' + resource => isRequestGroup(resource) && resource.parentId === '__WORKSPACE_ID__', ) as Workspace | undefined; if (importer.id === 'postman' && postmanTopLevelFolder) { await importResourcesToNewWorkspace({ @@ -239,19 +235,21 @@ export async function importResourcesToProject({ // if the resource is postman environment, if (importer.id === postmanEnvImporterId && resources.find(isEnvironment)) { - await Promise.all(resources.filter(isEnvironment).map(resource => - importResourcesToNewWorkspace({ - projectId, - resourceCacheItem, - workspaceToImport: { - name: resource.name, - scope: 'environment', - // __BASE_ENVIRONMENT_ID__ is the default parentId for environment imported by postman env importer, we use it to indicate the new workspace id - _id: '__BASE_ENVIRONMENT_ID__', - } as Workspace, - syncNewWorkspaceIfNeeded, - }) - )); + await Promise.all( + resources.filter(isEnvironment).map(resource => + importResourcesToNewWorkspace({ + projectId, + resourceCacheItem, + workspaceToImport: { + name: resource.name, + scope: 'environment', + // __BASE_ENVIRONMENT_ID__ is the default parentId for environment imported by postman env importer, we use it to indicate the new workspace id + _id: '__BASE_ENVIRONMENT_ID__', + } as Workspace, + syncNewWorkspaceIfNeeded, + }), + ), + ); continue; } @@ -268,10 +266,12 @@ export async function importResourcesToProject({ } // One or more workspaces in one resourceCacheItem(A resourceCacheItem corresponds to an import file), filter in the resources that belong to each workspace and then import to new workspaces respectively - await Promise.all(workspaceResources - .map(workspace => { + await Promise.all( + workspaceResources.map(workspace => { if (workspaceResources.filter(({ _id }) => _id === '__WORKSPACE_ID__').length > 1) { - console.warn(`There are more than one workspace with id __WORKSPACE_ID__ in the resources, the importer is ${resourceCacheItem.importer.name}`); + console.warn( + `There are more than one workspace with id __WORKSPACE_ID__ in the resources, the importer is ${resourceCacheItem.importer.name}`, + ); } // Here if there is only one workspace in the resources, we import all resources to it let resourcesInCurrentWorkspace = resources; @@ -288,17 +288,15 @@ export async function importResourcesToProject({ workspaceToImport: workspace, syncNewWorkspaceIfNeeded, }); - })); + }), + ); await db.flushChanges(bufferId); } } // Filter resources that belong to the workspace, including the workspace itself -function filterResourcesInWorkspace( - resources: BaseModel[], - workspace: Workspace, -) { +function filterResourcesInWorkspace(resources: BaseModel[], workspace: Workspace) { const workspaceId = workspace._id; const idToParentIdMap = new Map(); resources.forEach(resource => { @@ -325,7 +323,7 @@ function filterResourcesInWorkspace( const isTeamOrAbove = async () => { const { accountId } = await userSession.getOrCreate(); - const currentPlan = JSON.parse(localStorage.getItem(`${accountId}:currentPlan`) || '{}') as CurrentPlan || {}; + const currentPlan = (JSON.parse(localStorage.getItem(`${accountId}:currentPlan`) || '{}') as CurrentPlan) || {}; return ['team', 'enterprise', 'enterprise-member'].includes(currentPlan?.type); }; const updateIdsInString = (str: string, ResourceIdMap: Map) => { @@ -337,14 +335,15 @@ const updateIdsInString = (str: string, ResourceIdMap: Map) => { }; const importRequestWithNewIds = (request: Request, ResourceIdMap: Map, canTransform: boolean) => { let transformedRequest = request; - if (canTransform) { // if not logged in, this wont run + if (canTransform) { + // if not logged in, this wont run transformedRequest = JSON.parse(updateIdsInString(JSON.stringify(request), ResourceIdMap)); } - return ({ + return { ...transformedRequest, _id: ResourceIdMap.get(request._id), parentId: ResourceIdMap.get(request.parentId), - }); + }; }; export const importResourcesToWorkspace = async ({ workspaceId }: { workspaceId: string }) => { @@ -355,10 +354,7 @@ export const importResourcesToWorkspace = async ({ workspaceId }: { workspaceId: const ResourceIdMap = new Map(); const existingWorkspace = await models.workspace.getById(workspaceId); - invariant( - existingWorkspace, - `Could not find workspace with id ${workspaceId}` - ); + invariant(existingWorkspace, `Could not find workspace with id ${workspaceId}`); // Map new IDs ResourceIdMap.set(workspaceId, existingWorkspace._id); ResourceIdMap.set('__WORKSPACE_ID__', existingWorkspace._id); @@ -366,17 +362,15 @@ export const importResourcesToWorkspace = async ({ workspaceId }: { workspaceId: toImport && ResourceIdMap.set(toImport._id, existingWorkspace._id); const optionalResources = resources.filter( - resource => - !isWorkspace(resource) && - !isApiSpec(resource) && - !isCookieJar(resource) && - !isEnvironment(resource) + resource => !isWorkspace(resource) && !isApiSpec(resource) && !isCookieJar(resource) && !isEnvironment(resource), ); const baseEnvironment = await models.environment.getOrCreateForParentId(workspaceId); invariant(baseEnvironment, 'Could not create base environment'); - const baseEnvironmentFromResources = resources.filter(isEnvironment).find(env => env.parentId && env.parentId.startsWith('__WORKSPACE_ID__')); + const baseEnvironmentFromResources = resources + .filter(isEnvironment) + .find(env => env.parentId && env.parentId.startsWith('__WORKSPACE_ID__')); if (baseEnvironmentFromResources) { await models.environment.update(baseEnvironment, { data: baseEnvironmentFromResources.data }); } @@ -436,22 +430,19 @@ export const importResourcesToWorkspace = async ({ workspaceId }: { workspaceId: } }; -export const isApiSpecImport = ({ id }: Pick) => - id === 'openapi3' || id === 'swagger2'; +export const isApiSpecImport = ({ id }: Pick) => id === 'openapi3' || id === 'swagger2'; -const importResourcesToNewWorkspace = async ( - { - projectId, - resourceCacheItem, - workspaceToImport, - syncNewWorkspaceIfNeeded, - }: { - projectId: string; - resourceCacheItem: ResourceCacheType; - workspaceToImport?: Workspace; - syncNewWorkspaceIfNeeded?: (workspace: Workspace) => Promise; - } -) => { +const importResourcesToNewWorkspace = async ({ + projectId, + resourceCacheItem, + workspaceToImport, + syncNewWorkspaceIfNeeded, +}: { + projectId: string; + resourceCacheItem: ResourceCacheType; + workspaceToImport?: Workspace; + syncNewWorkspaceIfNeeded?: (workspace: Workspace) => Promise; +}) => { invariant(resourceCacheItem, 'No resources to import'); const project = await models.project.getById(projectId); @@ -504,7 +495,6 @@ const importResourcesToNewWorkspace = async ( contentType: apiSpec.contentType, fileName: workspaceToImport?.name, }); - } // If we're importing into a new workspace @@ -513,7 +503,7 @@ const importResourcesToNewWorkspace = async ( workspaceToImport && ResourceIdMap.set(workspaceToImport._id, newWorkspace._id); const resourcesWithoutWorkspaceAndApiSpec = resources.filter( - resource => !isWorkspace(resource) && !isApiSpec(resource) + resource => !isWorkspace(resource) && !isApiSpec(resource), ); for (const resource of resourcesWithoutWorkspaceAndApiSpec) { @@ -564,9 +554,7 @@ const importResourcesToNewWorkspace = async ( const firstSubEnvironment = subEnvironments[0]; if (firstSubEnvironment) { - const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId( - newWorkspace._id - ); + const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(newWorkspace._id); await models.workspaceMeta.update(workspaceMeta, { activeEnvironmentId: ResourceIdMap.get(firstSubEnvironment._id), diff --git a/packages/insomnia/src/common/insomnia-v5.ts b/packages/insomnia/src/common/insomnia-v5.ts index 5494d9c803..44d1620f8a 100644 --- a/packages/insomnia/src/common/insomnia-v5.ts +++ b/packages/insomnia/src/common/insomnia-v5.ts @@ -13,14 +13,41 @@ import type { UnitTest } from '../models/unit-test'; import type { UnitTestSuite } from '../models/unit-test-suite'; import type { WebSocketRequest } from '../models/websocket-request'; import type { Workspace, WorkspaceScope } from '../models/workspace'; -import { EXPORT_TYPE_API_SPEC, EXPORT_TYPE_COOKIE_JAR, EXPORT_TYPE_ENVIRONMENT, EXPORT_TYPE_GRPC_REQUEST, EXPORT_TYPE_MOCK_ROUTE, EXPORT_TYPE_MOCK_SERVER, EXPORT_TYPE_REQUEST, EXPORT_TYPE_REQUEST_GROUP, EXPORT_TYPE_UNIT_TEST, EXPORT_TYPE_UNIT_TEST_SUITE, EXPORT_TYPE_WEBSOCKET_REQUEST, EXPORT_TYPE_WORKSPACE } from './constants'; +import { + EXPORT_TYPE_API_SPEC, + EXPORT_TYPE_COOKIE_JAR, + EXPORT_TYPE_ENVIRONMENT, + EXPORT_TYPE_GRPC_REQUEST, + EXPORT_TYPE_MOCK_ROUTE, + EXPORT_TYPE_MOCK_SERVER, + EXPORT_TYPE_REQUEST, + EXPORT_TYPE_REQUEST_GROUP, + EXPORT_TYPE_UNIT_TEST, + EXPORT_TYPE_UNIT_TEST_SUITE, + EXPORT_TYPE_WEBSOCKET_REQUEST, + EXPORT_TYPE_WORKSPACE, +} from './constants'; import { database } from './database'; -import { type InsomniaFile, insomniaFileSchema, type Meta, WebsocketRequestSchema, type Z_GRPCRequest, type Z_Request, type Z_RequestGroup, type Z_WebsocketRequest } from './import-v5-parser'; +import { + type InsomniaFile, + insomniaFileSchema, + type Meta, + WebsocketRequestSchema, + type Z_GRPCRequest, + type Z_Request, + type Z_RequestGroup, + type Z_WebsocketRequest, +} from './import-v5-parser'; type WithExportType = T & { _type: string }; function filterEmptyValue(value: string | number | boolean | null | undefined) { - return value !== null && value !== undefined && value !== '' && !(typeof value === 'object' && Object.keys(value).length === 0); + return ( + value !== null && + value !== undefined && + value !== '' && + !(typeof value === 'object' && Object.keys(value).length === 0) + ); } function removeEmptyFields(data: any): any { @@ -67,14 +94,15 @@ export function insomniaSchemaTypeToScope(type: InsomniaFile['type']): Workspace return 'design'; } return 'mock-server'; - } function getWorkspace(file: InsomniaFile): WithExportType { return { - ...mapMetaToInsomniaMeta(file.meta || { - id: '__WORKSPACE_ID__', - }), + ...mapMetaToInsomniaMeta( + file.meta || { + id: '__WORKSPACE_ID__', + }, + ), type: 'Workspace', _type: EXPORT_TYPE_WORKSPACE, name: file.name || 'Imported Collection', @@ -86,30 +114,35 @@ function getWorkspace(file: InsomniaFile): WithExportType { function getEnvironments(file: InsomniaFile): Environment[] { if ('environments' in file && file.environments) { const baseEnvironment: WithExportType = { - ...mapMetaToInsomniaMeta(file.environments.meta || { - id: '__ENVIRONMENT_ID__', - }), + ...mapMetaToInsomniaMeta( + file.environments.meta || { + id: '__ENVIRONMENT_ID__', + }, + ), type: 'Environment', _type: EXPORT_TYPE_ENVIRONMENT, parentId: file.meta?.id || '__WORKSPACE_ID__', color: file.environments.color || null, - data: file.environments.data as Record || {}, - dataPropertyOrder: file.environments.dataPropertyOrder as Record || undefined, + data: (file.environments.data as Record) || {}, + dataPropertyOrder: (file.environments.dataPropertyOrder as Record) || undefined, name: file.environments.name || 'Base Environment', }; - const subEnvironments: WithExportType[] = file.environments.subEnvironments?.map((environment, index) => ({ - ...mapMetaToInsomniaMeta(environment.meta || { - id: '__ENVIRONMENT_ID__', - }), - type: 'Environment', - _type: EXPORT_TYPE_ENVIRONMENT, - color: environment.color || null, - data: environment.data as Record || {}, - dataPropertyOrder: environment.dataPropertyOrder as Record || undefined, - name: environment.name || `Environment ${index}`, - parentId: baseEnvironment._id, - })) || []; + const subEnvironments: WithExportType[] = + file.environments.subEnvironments?.map((environment, index) => ({ + ...mapMetaToInsomniaMeta( + environment.meta || { + id: '__ENVIRONMENT_ID__', + }, + ), + type: 'Environment', + _type: EXPORT_TYPE_ENVIRONMENT, + color: environment.color || null, + data: (environment.data as Record) || {}, + dataPropertyOrder: (environment.dataPropertyOrder as Record) || undefined, + name: environment.name || `Environment ${index}`, + parentId: baseEnvironment._id, + })) || []; return [baseEnvironment, ...subEnvironments]; } @@ -120,9 +153,11 @@ function getEnvironments(file: InsomniaFile): Environment[] { function getCookieJar(file: InsomniaFile): [CookieJar] | [] { if ('cookieJar' in file && file.cookieJar) { const cookieJar: WithExportType = { - ...mapMetaToInsomniaMeta(file.cookieJar.meta || { - id: '__COOKIE_JAR_ID__', - }), + ...mapMetaToInsomniaMeta( + file.cookieJar.meta || { + id: '__COOKIE_JAR_ID__', + }, + ), type: 'CookieJar', _type: EXPORT_TYPE_COOKIE_JAR, name: file.cookieJar.name || 'Imported Cookie Jar', @@ -138,18 +173,22 @@ function getCookieJar(file: InsomniaFile): [CookieJar] | [] { function getApiSpec(file: InsomniaFile): [WithExportType] | [] { if ('spec' in file && file.spec) { - return [{ - ...mapMetaToInsomniaMeta(file.spec.meta || { - id: '__API_SPEC_ID__', - }), - type: 'ApiSpec', - name: file.name || 'Api Spec', - _type: EXPORT_TYPE_API_SPEC, - fileName: 'file' in file.spec ? file.spec.file : '', - contentType: 'json', - contents: 'contents' in file.spec && file.spec.contents ? stringify(file.spec.contents) : '', - parentId: file.meta?.id || '__WORKSPACE_ID__', - }]; + return [ + { + ...mapMetaToInsomniaMeta( + file.spec.meta || { + id: '__API_SPEC_ID__', + }, + ), + type: 'ApiSpec', + name: file.name || 'Api Spec', + _type: EXPORT_TYPE_API_SPEC, + fileName: 'file' in file.spec ? file.spec.file : '', + contentType: 'json', + contents: 'contents' in file.spec && file.spec.contents ? stringify(file.spec.contents) : '', + parentId: file.meta?.id || '__WORKSPACE_ID__', + }, + ]; } return []; @@ -158,9 +197,11 @@ function getApiSpec(file: InsomniaFile): [WithExportType] | [] { function getMockServer(file: InsomniaFile): WithExportType { if (file.type === 'mock.insomnia.rest/5.0') { return { - ...mapMetaToInsomniaMeta(file.server?.meta || { - id: '__MOCK_SERVER_ID__', - }), + ...mapMetaToInsomniaMeta( + file.server?.meta || { + id: '__MOCK_SERVER_ID__', + }, + ), type: 'MockServer', _type: EXPORT_TYPE_MOCK_SERVER, name: file.name || 'Imported Mock Server', @@ -175,21 +216,25 @@ function getMockServer(file: InsomniaFile): WithExportType { function getMockRoutes(file: InsomniaFile): WithExportType[] { if (file.type === 'mock.insomnia.rest/5.0') { - return file.routes?.map(mock => ({ - ...mapMetaToInsomniaMeta(mock.meta || { - id: '__MOCK_ROUTE_ID__', - }), - type: 'MockRoute', - _type: EXPORT_TYPE_MOCK_ROUTE, - name: mock.name || 'Imported Mock Route', - parentId: file.server?.meta?.id || '__MOCK_SERVER_ID__', - body: mock.body || '', - headers: mock.headers || [], - method: mock.method || '', - mimeType: mock.mimeType || '', - statusCode: mock.statusCode, - statusText: mock.statusText || '', - })) || []; + return ( + file.routes?.map(mock => ({ + ...mapMetaToInsomniaMeta( + mock.meta || { + id: '__MOCK_ROUTE_ID__', + }, + ), + type: 'MockRoute', + _type: EXPORT_TYPE_MOCK_ROUTE, + name: mock.name || 'Imported Mock Route', + parentId: file.server?.meta?.id || '__MOCK_SERVER_ID__', + body: mock.body || '', + headers: mock.headers || [], + method: mock.method || '', + mimeType: mock.mimeType || '', + statusCode: mock.statusCode, + statusText: mock.statusText || '', + })) || [] + ); } return []; @@ -201,9 +246,11 @@ function getTestSuites(file: InsomniaFile): (UnitTestSuite | UnitTest)[] { file.testSuites?.forEach((testSuite, index) => { const suite: WithExportType = { - ...mapMetaToInsomniaMeta(testSuite.meta || { - id: '__UNIT_TEST_SUITE_ID__', - }), + ...mapMetaToInsomniaMeta( + testSuite.meta || { + id: '__UNIT_TEST_SUITE_ID__', + }, + ), type: 'UnitTestSuite', _type: EXPORT_TYPE_UNIT_TEST_SUITE, name: testSuite.name || 'Imported Test Suite', @@ -213,18 +260,21 @@ function getTestSuites(file: InsomniaFile): (UnitTestSuite | UnitTest)[] { resources.push(suite); - const tests: WithExportType[] = testSuite.tests?.map((test, index) => ({ - ...mapMetaToInsomniaMeta(test.meta || { - id: '__UNIT_TEST_ID__', - }), - type: 'UnitTest', - _type: EXPORT_TYPE_UNIT_TEST, - name: test.name || 'Imported Test', - parentId: suite._id, - requestId: test.requestId, - code: test.code, - metaSortKey: test.meta?.sortKey ?? index, - })) || []; + const tests: WithExportType[] = + testSuite.tests?.map((test, index) => ({ + ...mapMetaToInsomniaMeta( + test.meta || { + id: '__UNIT_TEST_ID__', + }, + ), + type: 'UnitTest', + _type: EXPORT_TYPE_UNIT_TEST, + name: test.name || 'Imported Test', + parentId: suite._id, + requestId: test.requestId, + code: test.code, + metaSortKey: test.meta?.sortKey ?? index, + })) || []; resources.push(...tests); }); @@ -239,13 +289,18 @@ function getCollection(file: InsomniaFile): (Request | WebSocketRequest | GrpcRe if (file.type === 'collection.insomnia.rest/5.0' || file.type === 'spec.insomnia.rest/5.0') { const resources: (Request | WebSocketRequest | GrpcRequest | RequestGroup)[] = []; - function walkCollection(collection: Extract['collection'], parentId: string) { + function walkCollection( + collection: Extract['collection'], + parentId: string, + ) { collection?.forEach(item => { if ('children' in item && item.children) { const requestGroup: WithExportType = { - ...mapMetaToInsomniaMeta(item.meta || { - id: '__REQUEST_GROUP_ID__', - }), + ...mapMetaToInsomniaMeta( + item.meta || { + id: '__REQUEST_GROUP_ID__', + }, + ), type: 'RequestGroup', _type: EXPORT_TYPE_REQUEST_GROUP, name: item.name || 'Imported Folder', @@ -253,9 +308,9 @@ function getCollection(file: InsomniaFile): (Request | WebSocketRequest | GrpcRe preRequestScript: item.scripts?.preRequest || '', afterResponseScript: item.scripts?.afterResponse || '', authentication: item.authentication || {}, - environment: item.environment as Record || {}, + environment: (item.environment as Record) || {}, // 🚧 WARNING 🚧 If we set the order to an empty object instead of undefined it will remove the environment from the folder due to filtering logic (related to json-order) - environmentPropertyOrder: item.environmentPropertyOrder as Record || undefined, + environmentPropertyOrder: (item.environmentPropertyOrder as Record) || undefined, }; resources.push(requestGroup); @@ -263,9 +318,11 @@ function getCollection(file: InsomniaFile): (Request | WebSocketRequest | GrpcRe walkCollection(item.children, requestGroup._id); } else if ('method' in item && item.method) { const request: WithExportType = { - ...mapMetaToInsomniaMeta(item.meta || { - id: '__REQUEST_ID__', - }), + ...mapMetaToInsomniaMeta( + item.meta || { + id: '__REQUEST_ID__', + }, + ), type: 'Request', _type: EXPORT_TYPE_REQUEST, name: item.name || 'Imported Request', @@ -291,9 +348,11 @@ function getCollection(file: InsomniaFile): (Request | WebSocketRequest | GrpcRe resources.push(request); } else if ('reflectionApi' in item) { const grpcRequest: WithExportType = { - ...mapMetaToInsomniaMeta(item.meta || { - id: '__GRPC_REQUEST_ID__', - }), + ...mapMetaToInsomniaMeta( + item.meta || { + id: '__GRPC_REQUEST_ID__', + }, + ), type: 'GrpcRequest', _type: EXPORT_TYPE_GRPC_REQUEST, name: item.name || 'Imported gRPC Request', @@ -318,9 +377,11 @@ function getCollection(file: InsomniaFile): (Request | WebSocketRequest | GrpcRe if (wbRequest.success) { const data = wbRequest.data; const websocketRequest: WithExportType = { - ...mapMetaToInsomniaMeta(data.meta || { - id: '__WEBSOCKET_REQUEST_ID__', - }), + ...mapMetaToInsomniaMeta( + data.meta || { + id: '__WEBSOCKET_REQUEST_ID__', + }, + ), type: 'WebSocketRequest', _type: EXPORT_TYPE_WEBSOCKET_REQUEST, name: item.name || 'Imported WebSocket Request', @@ -356,12 +417,7 @@ export function importInsomniaV5Data(rawData: string) { const file = insomniaFileSchema.parse(parse(rawData)); if (file.type === 'collection.insomnia.rest/5.0') { - return [ - getWorkspace(file), - ...getEnvironments(file), - ...getCookieJar(file), - ...getCollection(file), - ]; + return [getWorkspace(file), ...getEnvironments(file), ...getCookieJar(file), ...getCollection(file)]; } if (file.type === 'spec.insomnia.rest/5.0') { @@ -376,17 +432,10 @@ export function importInsomniaV5Data(rawData: string) { } if (file.type === 'environment.insomnia.rest/5.0') { - return [ - getWorkspace(file), - ...getEnvironments(file), - ]; + return [getWorkspace(file), ...getEnvironments(file)]; } - return [ - getWorkspace(file), - getMockServer(file), - ...getMockRoutes(file), - ]; + return [getWorkspace(file), getMockServer(file), ...getMockRoutes(file)]; } catch (err) { console.error('Failed to import Insomnia v5 data', err); return []; @@ -397,9 +446,12 @@ export async function getInsomniaV5DataExport({ workspaceId, includePrivateEnvironments, requestIds, -}: { workspaceId: string; includePrivateEnvironments: boolean; requestIds?: string[] }) { +}: { + workspaceId: string; + includePrivateEnvironments: boolean; + requestIds?: string[]; +}) { try { - const workspace = await models.workspace.getById(workspaceId); if (!workspace) { @@ -418,131 +470,142 @@ export async function getInsomniaV5DataExport({ return false; }); - function getCollectionFromResources(resources: (Request | RequestGroup | WebSocketRequest | GrpcRequest)[], parentId: string): Extract['collection'] { + function getCollectionFromResources( + resources: (Request | RequestGroup | WebSocketRequest | GrpcRequest)[], + parentId: string, + ): Extract['collection'] { const collection: Extract['collection'] = []; - resources.filter(resource => { - if (!requestIds || requestIds.length === 0 || models.requestGroup.isRequestGroup(resource)) { - return true; - } + resources + .filter(resource => { + if (!requestIds || requestIds.length === 0 || models.requestGroup.isRequestGroup(resource)) { + return true; + } - return requestIds.includes(resource._id); - }).filter(resource => resource.parentId === parentId).forEach(resource => { - if (models.request.isRequest(resource)) { - const request: Z_Request = { - url: resource.url, - name: resource.name, - meta: { - id: resource._id, - created: resource.created, - modified: resource.modified, - isPrivate: resource.isPrivate, - description: resource.description, - sortKey: resource.metaSortKey, - }, - method: resource.method, - body: resource.body, - parameters: resource.parameters, - headers: resource.headers, - authentication: resource.authentication, - scripts: { - preRequest: resource.preRequestScript, - afterResponse: resource.afterResponseScript, - }, - settings: { - renderRequestBody: !resource.settingDisableRenderRequestBody, - encodeUrl: resource.settingEncodeUrl, - followRedirects: resource.settingFollowRedirects, - cookies: { - send: resource.settingSendCookies, - store: resource.settingStoreCookies, + return requestIds.includes(resource._id); + }) + .filter(resource => resource.parentId === parentId) + .forEach(resource => { + if (models.request.isRequest(resource)) { + const request: Z_Request = { + url: resource.url, + name: resource.name, + meta: { + id: resource._id, + created: resource.created, + modified: resource.modified, + isPrivate: resource.isPrivate, + description: resource.description, + sortKey: resource.metaSortKey, }, - rebuildPath: resource.settingRebuildPath, - }, - pathParameters: resource.pathParameters, - }; - collection.push(request); - } else if (models.requestGroup.isRequestGroup(resource)) { - const requestGroup: Z_RequestGroup = { - name: resource.name, - meta: { - id: resource._id, - created: resource.created, - modified: resource.modified, - isPrivate: resource.isPrivate, - sortKey: resource.metaSortKey, - description: resource.description, - }, - children: getCollectionFromResources(resources, resource._id), - scripts: { - afterResponse: resource.afterResponseScript, - preRequest: resource.preRequestScript, - }, - authentication: resource.authentication, - environment: resource.environment, - environmentPropertyOrder: resource.environmentPropertyOrder, - headers: resource.headers, - }; - collection.push(requestGroup); - } else if (models.webSocketRequest.isWebSocketRequest(resource)) { - const webSocketRequest: Z_WebsocketRequest = { - url: resource.url, - name: resource.name, - meta: { - id: resource._id, - created: resource.created, - modified: resource.modified, - isPrivate: resource.isPrivate, - description: resource.description, - sortKey: resource.metaSortKey, - }, - settings: { - encodeUrl: resource.settingEncodeUrl, - followRedirects: resource.settingFollowRedirects, - cookies: { - send: resource.settingSendCookies, - store: resource.settingStoreCookies, + method: resource.method, + body: resource.body, + parameters: resource.parameters, + headers: resource.headers, + authentication: resource.authentication, + scripts: { + preRequest: resource.preRequestScript, + afterResponse: resource.afterResponseScript, }, - }, - authentication: resource.authentication, - headers: resource.headers, - parameters: resource.parameters, - pathParameters: resource.pathParameters, - }; - collection.push(webSocketRequest); - } else if (models.grpcRequest.isGrpcRequest(resource)) { - const grpcRequest: Z_GRPCRequest = { - url: resource.url, - name: resource.name, - meta: { - id: resource._id, - created: resource.created, - modified: resource.modified, - isPrivate: resource.isPrivate, - sortKey: resource.metaSortKey, - description: resource.description, - }, - body: resource.body, - metadata: resource.metadata, - protoFileId: resource.protoFileId, - protoMethodName: resource.protoMethodName, - reflectionApi: resource.reflectionApi, - }; + settings: { + renderRequestBody: !resource.settingDisableRenderRequestBody, + encodeUrl: resource.settingEncodeUrl, + followRedirects: resource.settingFollowRedirects, + cookies: { + send: resource.settingSendCookies, + store: resource.settingStoreCookies, + }, + rebuildPath: resource.settingRebuildPath, + }, + pathParameters: resource.pathParameters, + }; + collection.push(request); + } else if (models.requestGroup.isRequestGroup(resource)) { + const requestGroup: Z_RequestGroup = { + name: resource.name, + meta: { + id: resource._id, + created: resource.created, + modified: resource.modified, + isPrivate: resource.isPrivate, + sortKey: resource.metaSortKey, + description: resource.description, + }, + children: getCollectionFromResources(resources, resource._id), + scripts: { + afterResponse: resource.afterResponseScript, + preRequest: resource.preRequestScript, + }, + authentication: resource.authentication, + environment: resource.environment, + environmentPropertyOrder: resource.environmentPropertyOrder, + headers: resource.headers, + }; + collection.push(requestGroup); + } else if (models.webSocketRequest.isWebSocketRequest(resource)) { + const webSocketRequest: Z_WebsocketRequest = { + url: resource.url, + name: resource.name, + meta: { + id: resource._id, + created: resource.created, + modified: resource.modified, + isPrivate: resource.isPrivate, + description: resource.description, + sortKey: resource.metaSortKey, + }, + settings: { + encodeUrl: resource.settingEncodeUrl, + followRedirects: resource.settingFollowRedirects, + cookies: { + send: resource.settingSendCookies, + store: resource.settingStoreCookies, + }, + }, + authentication: resource.authentication, + headers: resource.headers, + parameters: resource.parameters, + pathParameters: resource.pathParameters, + }; + collection.push(webSocketRequest); + } else if (models.grpcRequest.isGrpcRequest(resource)) { + const grpcRequest: Z_GRPCRequest = { + url: resource.url, + name: resource.name, + meta: { + id: resource._id, + created: resource.created, + modified: resource.modified, + isPrivate: resource.isPrivate, + sortKey: resource.metaSortKey, + description: resource.description, + }, + body: resource.body, + metadata: resource.metadata, + protoFileId: resource.protoFileId, + protoMethodName: resource.protoMethodName, + reflectionApi: resource.reflectionApi, + }; - collection.push(grpcRequest); - } - }); + collection.push(grpcRequest); + } + }); return collection; } - function getEnvironmentsFromResources(resources: Environment[], includePrivateEnvironments: boolean): Extract['environments'] { + function getEnvironmentsFromResources( + resources: Environment[], + includePrivateEnvironments: boolean, + ): Extract['environments'] { const baseEnvironment = resources.find(environment => environment.parentId.startsWith('wrk_')); if (!baseEnvironment) { throw new Error('Base environment not found'); } - const subEnvironments = resources.filter(environment => environment.parentId === baseEnvironment?._id).filter(environment => includePrivateEnvironments || !environment.isPrivate); + const subEnvironments = resources + .filter(environment => environment.parentId === baseEnvironment?._id) + .filter(environment => includePrivateEnvironments || !environment.isPrivate); return { name: baseEnvironment.name, @@ -569,7 +632,9 @@ export async function getInsomniaV5DataExport({ }; } - function getCookieJarFromResources(resources: CookieJar[]): Extract['cookieJar'] { + function getCookieJarFromResources( + resources: CookieJar[], + ): Extract['cookieJar'] { return resources.map(resource => ({ name: resource.name, meta: { @@ -585,7 +650,9 @@ export async function getInsomniaV5DataExport({ }))[0]; } - function getTestSuitesFromResources(resources: (UnitTestSuite | UnitTest)[]): Extract['testSuites'] { + function getTestSuitesFromResources( + resources: (UnitTestSuite | UnitTest)[], + ): Extract['testSuites'] { const testSuites: Extract['testSuites'] = []; resources.filter(models.unitTestSuite.isUnitTestSuite).forEach(testSuite => { @@ -618,7 +685,9 @@ export async function getInsomniaV5DataExport({ return testSuites; } - function getSpecFromResources(resources: ApiSpec[]): Extract['spec'] { + function getSpecFromResources( + resources: ApiSpec[], + ): Extract['spec'] { const spec = resources[0]; // const parser = spec.contentType === 'json' ? JSON.parse : parse; let contents = {}; @@ -646,7 +715,9 @@ export async function getInsomniaV5DataExport({ }; } - function getRoutesFromResources(resources: MockRoute[]): Extract['routes'] { + function getRoutesFromResources( + resources: MockRoute[], + ): Extract['routes'] { return resources.map(resource => ({ name: resource.name, meta: { @@ -675,9 +746,21 @@ export async function getInsomniaV5DataExport({ isPrivate: workspace.isPrivate, description: workspace.description, }, - collection: getCollectionFromResources(exportableResources.filter(resource => models.requestGroup.isRequestGroup(resource) || models.request.isRequest(resource) || models.webSocketRequest.isWebSocketRequest(resource) || models.grpcRequest.isGrpcRequest(resource)), workspace._id), + collection: getCollectionFromResources( + exportableResources.filter( + resource => + models.requestGroup.isRequestGroup(resource) || + models.request.isRequest(resource) || + models.webSocketRequest.isWebSocketRequest(resource) || + models.grpcRequest.isGrpcRequest(resource), + ), + workspace._id, + ), cookieJar: getCookieJarFromResources(exportableResources.filter(models.cookieJar.isCookieJar)), - environments: getEnvironmentsFromResources(exportableResources.filter(models.environment.isEnvironment), includePrivateEnvironments), + environments: getEnvironmentsFromResources( + exportableResources.filter(models.environment.isEnvironment), + includePrivateEnvironments, + ), }; return stringify(removeEmptyFields(collection)); @@ -692,11 +775,27 @@ export async function getInsomniaV5DataExport({ isPrivate: workspace.isPrivate, description: workspace.description, }, - collection: getCollectionFromResources(exportableResources.filter(resource => models.requestGroup.isRequestGroup(resource) || models.request.isRequest(resource) || models.webSocketRequest.isWebSocketRequest(resource) || models.grpcRequest.isGrpcRequest(resource)), workspace._id), + collection: getCollectionFromResources( + exportableResources.filter( + resource => + models.requestGroup.isRequestGroup(resource) || + models.request.isRequest(resource) || + models.webSocketRequest.isWebSocketRequest(resource) || + models.grpcRequest.isGrpcRequest(resource), + ), + workspace._id, + ), cookieJar: getCookieJarFromResources(exportableResources.filter(models.cookieJar.isCookieJar)), - environments: getEnvironmentsFromResources(exportableResources.filter(models.environment.isEnvironment), includePrivateEnvironments), + environments: getEnvironmentsFromResources( + exportableResources.filter(models.environment.isEnvironment), + includePrivateEnvironments, + ), spec: getSpecFromResources(exportableResources.filter(models.apiSpec.isApiSpec)), - testSuites: getTestSuitesFromResources(exportableResources.filter(resource => models.unitTestSuite.isUnitTestSuite(resource) || models.unitTest.isUnitTest(resource))), + testSuites: getTestSuitesFromResources( + exportableResources.filter( + resource => models.unitTestSuite.isUnitTestSuite(resource) || models.unitTest.isUnitTest(resource), + ), + ), }; return stringify(removeEmptyFields(spec)); @@ -711,7 +810,10 @@ export async function getInsomniaV5DataExport({ isPrivate: workspace.isPrivate, description: workspace.description, }, - environments: getEnvironmentsFromResources(exportableResources.filter(models.environment.isEnvironment), includePrivateEnvironments), + environments: getEnvironmentsFromResources( + exportableResources.filter(models.environment.isEnvironment), + includePrivateEnvironments, + ), }; return stringify(removeEmptyFields(environment)); @@ -744,7 +846,6 @@ export async function getInsomniaV5DataExport({ return stringify(removeEmptyFields(mockServer), {}); } throw new Error('Unknown workspace scope'); - } catch (err) { console.error('Failed to export Insomnia v5 data', err); return ''; diff --git a/packages/insomnia/src/common/misc.ts b/packages/insomnia/src/common/misc.ts index 35fdea4ac4..c8935192aa 100644 --- a/packages/insomnia/src/common/misc.ts +++ b/packages/insomnia/src/common/misc.ts @@ -87,7 +87,6 @@ export function generateId(prefix?: string) { return `${prefix}_${id}`; } return id; - } export function delay(milliseconds: number = DEBOUNCE_MILLIS) { @@ -98,15 +97,15 @@ export const debounce = ) => ReturnType>( func: F, waitFor: number = DEBOUNCE_MILLIS, ) => { - let timeout: NodeJS.Timeout + let timeout: NodeJS.Timeout; const debounced = (...args: Parameters) => { - clearTimeout(timeout) - timeout = setTimeout(() => func(...args), waitFor) - } + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), waitFor); + }; - return debounced -} + return debounced; +}; export function describeByteSize(bytes: number, long = false) { bytes = Math.round(bytes * 10) / 10; @@ -138,7 +137,6 @@ export function fnOrString(v: string | ((...args: any[]) => any), ...args: any[] return v; } return v(...args); - } export function compressObject(obj: any) { @@ -180,11 +178,7 @@ export function fuzzyMatch( return fuzzyMatchAll(searchString, [text], options); } -export function fuzzyMatchAll( - searchString: string, - allText: string[], - options: FuzzyMatchOptions = {}, -) { +export function fuzzyMatchAll(searchString: string, allText: string[], options: FuzzyMatchOptions = {}) { if (!searchString || !searchString.trim()) { return null; } @@ -239,9 +233,7 @@ export function fuzzyMatchAll( }; } -export function isNotNullOrUndefined( - value: ValueType | null | undefined -): value is ValueType { +export function isNotNullOrUndefined(value: ValueType | null | undefined): value is ValueType { if (value === null || value === undefined) { return false; } diff --git a/packages/insomnia/src/common/render.ts b/packages/insomnia/src/common/render.ts index 7203568a24..e8c1597d69 100644 --- a/packages/insomnia/src/common/render.ts +++ b/packages/insomnia/src/common/render.ts @@ -2,7 +2,12 @@ import clone from 'clone'; import orderedJSON from 'json-order'; import * as models from '../models'; -import { type Environment, type UserUploadEnvironment, vaultEnvironmentPath, vaultEnvironmentRuntimePath } from '../models/environment'; +import { + type Environment, + type UserUploadEnvironment, + vaultEnvironmentPath, + vaultEnvironmentRuntimePath, +} from '../models/environment'; import type { GrpcRequest, GrpcRequestBody } from '../models/grpc-request'; import { isProject } from '../models/project'; import { PATH_PARAMETER_REGEX, type Request } from '../models/request'; @@ -25,27 +30,25 @@ import { setDefaultProtocol } from '../utils/url/protocol'; import { CONTENT_TYPE_GRAPHQL, JSON_ORDER_SEPARATOR } from './constants'; import { database as db } from './database'; -export async function buildRenderContext( - { - ancestors, - rootEnvironment, - subEnvironment, - rootGlobalEnvironment, - subGlobalEnvironment, - userUploadEnvironment, - transientVariables, - baseContext, - }: { - ancestors?: RenderContextAncestor[]; - rootEnvironment?: Environment; - subEnvironment?: Environment; - rootGlobalEnvironment?: Environment | null; - subGlobalEnvironment?: Environment | null; - userUploadEnvironment?: UserUploadEnvironment; - transientVariables?: Environment; - baseContext: BaseRenderContext; - }, -): Promise { +export async function buildRenderContext({ + ancestors, + rootEnvironment, + subEnvironment, + rootGlobalEnvironment, + subGlobalEnvironment, + userUploadEnvironment, + transientVariables, + baseContext, +}: { + ancestors?: RenderContextAncestor[]; + rootEnvironment?: Environment; + subEnvironment?: Environment; + rootGlobalEnvironment?: Environment | null; + subGlobalEnvironment?: Environment | null; + userUploadEnvironment?: UserUploadEnvironment; + transientVariables?: Environment; + baseContext: BaseRenderContext; +}): Promise { const envObjects: Record[] = []; if (rootGlobalEnvironment) { @@ -70,20 +73,12 @@ export async function buildRenderContext( // Then get sub environment keys in correct order // Then get ancestor (folder) environment keys in correct order if (rootEnvironment) { - const ordered = orderedJSON.order( - rootEnvironment.data, - rootEnvironment.dataPropertyOrder, - JSON_ORDER_SEPARATOR, - ); + const ordered = orderedJSON.order(rootEnvironment.data, rootEnvironment.dataPropertyOrder, JSON_ORDER_SEPARATOR); envObjects.push(ordered); } if (subEnvironment) { - const ordered = orderedJSON.order( - subEnvironment.data, - subEnvironment.dataPropertyOrder, - JSON_ORDER_SEPARATOR, - ); + const ordered = orderedJSON.order(subEnvironment.data, subEnvironment.dataPropertyOrder, JSON_ORDER_SEPARATOR); envObjects.push(ordered); } @@ -92,11 +87,7 @@ export async function buildRenderContext( const { environment, environmentPropertyOrder } = ancestor; if (typeof environment === 'object' && environment !== null) { - const ordered = orderedJSON.order( - environment, - environmentPropertyOrder, - JSON_ORDER_SEPARATOR, - ); + const ordered = orderedJSON.order(environment, environmentPropertyOrder, JSON_ORDER_SEPARATOR); envObjects.push(ordered); } } @@ -129,10 +120,7 @@ export async function buildRenderContext( const renderContext = baseContext; // Made the rendering into a recursive function to handle nested Objects - async function renderSubContext( - subObject: Record, - subContext: BaseRenderContext, - ) { + async function renderSubContext(subObject: Record, subContext: BaseRenderContext) { const keys = _getOrderedEnvironmentKeys(subObject); for (const key of keys) { @@ -182,11 +170,17 @@ export async function buildRenderContext( finalRenderContext = await renderSubContext(envObject, finalRenderContext); } - finalRenderContext[vaultEnvironmentPath] = await maskOrDecryptVaultDataIfNecessary(finalRenderContext[vaultEnvironmentPath], renderContext?.getPurpose()); + finalRenderContext[vaultEnvironmentPath] = await maskOrDecryptVaultDataIfNecessary( + finalRenderContext[vaultEnvironmentPath], + renderContext?.getPurpose(), + ); // Merge all vault environments under vaultEnvironmentPath to vaultEnvironmentRuntimePath which is more human readable. // This will also keep all legacy environment variables defined under the vaultEnvironmentRuntimePath. if (finalRenderContext[vaultEnvironmentPath]) { - if (finalRenderContext[vaultEnvironmentRuntimePath] && typeof finalRenderContext[vaultEnvironmentRuntimePath] !== 'object') { + if ( + finalRenderContext[vaultEnvironmentRuntimePath] && + typeof finalRenderContext[vaultEnvironmentRuntimePath] !== 'object' + ) { const errorMsg = `${vaultEnvironmentRuntimePath} is a reserved key for insomnia vault, please rename your environment with vault as key.`; const newError = new RenderError(errorMsg); newError.type = 'render'; @@ -215,13 +209,7 @@ export async function buildRenderContext( continue; } - const renderResult = await render( - finalRenderContext[key], - finalRenderContext, - null, - 'keep', - 'Environment', - ); + const renderResult = await render(finalRenderContext[key], finalRenderContext, null, 'keep', 'Environment'); // Result didn't change, so skip if (renderResult === finalRenderContext[key]) { @@ -235,7 +223,12 @@ export async function buildRenderContext( return finalRenderContext; } -const renderInThisProcess = async (input: { input: string; context: BaseRenderContext; path: string; ignoreUndefinedEnvVariable: boolean }) => { +const renderInThisProcess = async (input: { + input: string; + context: BaseRenderContext; + path: string; + ignoreUndefinedEnvVariable: boolean; +}) => { return templating.render(input.input, { context: input.context, path: input.path, @@ -303,7 +296,8 @@ export async function render( const settings = await models.settings.get(); const pluginsAreRestrictedToRunInWorker = settings?.pluginsAllowElevatedAccess === false; - const currentProcessIsRendererAndPluginsAreRestricted = process.type === 'renderer' && pluginsAreRestrictedToRunInWorker; + const currentProcessIsRendererAndPluginsAreRestricted = + process.type === 'renderer' && pluginsAreRestrictedToRunInWorker; const renderFork = currentProcessIsRendererAndPluginsAreRestricted ? (await import('../ui/worker/templating-handler')).renderInWorker : renderInThisProcess; @@ -360,7 +354,9 @@ export async function render( const renderResult = await next(newObj, name, true); if (undefinedEnvironmentVariables.length > 0) { - const error = new RenderError(`Failed to render environment variables: ${undefinedEnvironmentVariables.join(', ')}`); + const error = new RenderError( + `Failed to render environment variables: ${undefinedEnvironmentVariables.join(', ')}`, + ); error.type = 'render'; error.extraInfo = { subType: 'environmentVariable', @@ -372,19 +368,17 @@ export async function render( return renderResult; } -export async function getRenderContext( - { - request, - environment, - baseEnvironment, - userUploadEnvironment, - transientVariables, - ancestors: _ancestors, - purpose, - extraInfo, - }: RenderContextOptions, -): Promise { - const ancestors = _ancestors || await getRenderContextAncestors(request); +export async function getRenderContext({ + request, + environment, + baseEnvironment, + userUploadEnvironment, + transientVariables, + ancestors: _ancestors, + purpose, + extraInfo, +}: RenderContextOptions): Promise { + const ancestors = _ancestors || (await getRenderContextAncestors(request)); const project = ancestors.find(isProject); const workspace = ancestors.find(isWorkspace); @@ -415,15 +409,14 @@ export async function getRenderContext( } } - const rootEnvironment = baseEnvironment || await models.environment.getOrCreateForParentId( - workspace ? workspace._id : 'n/a', - ); - const subEnvironmentId = environment ? - typeof environment === 'string' ? environment : environment._id : - 'n/a'; - const subEnvironment = environment ? - typeof environment === 'string' ? await models.environment.getById(environment) : environment : - await models.environment.getById('n/a'); + const rootEnvironment = + baseEnvironment || (await models.environment.getOrCreateForParentId(workspace ? workspace._id : 'n/a')); + const subEnvironmentId = environment ? (typeof environment === 'string' ? environment : environment._id) : 'n/a'; + const subEnvironment = environment + ? typeof environment === 'string' + ? await models.environment.getById(environment) + : environment + : await models.environment.getById('n/a'); const keySource: Record = {}; // Function that gets Keys and stores their Source location @@ -515,15 +508,13 @@ export async function getRenderContext( }); } -export async function getRenderedGrpcRequest( - { - purpose, - extraInfo, - request, - environment, - skipBody, - }: BaseRenderContextOptions & { request: GrpcRequest; skipBody?: boolean }, -) { +export async function getRenderedGrpcRequest({ + purpose, + extraInfo, + request, + environment, + skipBody, +}: BaseRenderContextOptions & { request: GrpcRequest; skipBody?: boolean }) { const renderContext = await getRenderContext({ request, environment, purpose, extraInfo }); const description = request.description; // Render description separately because it's lower priority @@ -531,41 +522,33 @@ export async function getRenderedGrpcRequest( // Ignore body by default and only include if specified to const ignorePathRegex = skipBody ? /^body.*/ : null; // Render all request properties - const renderedRequest: GrpcRequest = await render( - request, - renderContext, - ignorePathRegex, - ); + const renderedRequest: GrpcRequest = await render(request, renderContext, ignorePathRegex); renderedRequest.description = await render(description, renderContext, null, 'keep'); return renderedRequest; } -export async function getRenderedGrpcRequestMessage( - { - environment, - request, - extraInfo, - purpose, - }: BaseRenderContextOptions & { request: GrpcRequest }, -) { +export async function getRenderedGrpcRequestMessage({ + environment, + request, + extraInfo, + purpose, +}: BaseRenderContextOptions & { request: GrpcRequest }) { const renderContext = await getRenderContext({ request, environment, purpose, extraInfo }); // Render request body const renderedBody: GrpcRequestBody = await render(request.body, renderContext); return renderedBody; } -export async function getRenderedRequestAndContext( - { - request, - environment, - baseEnvironment, - userUploadEnvironment, - transientVariables, - extraInfo, - purpose, - ignoreUndefinedEnvVariable, - }: BaseRenderContextOptions & { request: Request }, -): Promise<{ +export async function getRenderedRequestAndContext({ + request, + environment, + baseEnvironment, + userUploadEnvironment, + transientVariables, + extraInfo, + purpose, + ignoreUndefinedEnvVariable, +}: BaseRenderContextOptions & { request: Request }): Promise<{ request: RenderedRequest; context: Record; }> { @@ -575,7 +558,16 @@ export async function getRenderedRequestAndContext( const parentId = workspace ? workspace._id : 'n/a'; const cookieJar = await models.cookieJar.getOrCreateForParentId(parentId); - const renderContext = await getRenderContext({ request, environment, ancestors, purpose, extraInfo, baseEnvironment, userUploadEnvironment, transientVariables }); + const renderContext = await getRenderContext({ + request, + environment, + ancestors, + purpose, + extraInfo, + baseEnvironment, + userUploadEnvironment, + transientVariables, + }); // HACK: Switch '#}' to '# }' to prevent Nunjucks from barfing // https://github.com/kong/insomnia/issues/895 @@ -585,7 +577,7 @@ export async function getRenderedRequestAndContext( o.query = o.query.replace(/#}/g, '# }'); request.body.text = JSON.stringify(o); } - } catch (err) { } + } catch (err) {} // Render description separately because it's lower priority const description = request.description; @@ -624,7 +616,11 @@ export async function getRenderedRequestAndContext( } // Remove disabled authentication - if (renderedRequest.authentication && 'disabled' in renderedRequest.authentication && renderedRequest.authentication.disabled) { + if ( + renderedRequest.authentication && + 'disabled' in renderedRequest.authentication && + renderedRequest.authentication.disabled + ) { renderedRequest.authentication = {}; } @@ -704,7 +700,9 @@ function _getOrderedEnvironmentKeys(finalRenderContext: Record): st }); } -export async function getRenderContextAncestors(base?: Request | GrpcRequest | WebSocketRequest | Workspace): Promise { +export async function getRenderContextAncestors( + base?: Request | GrpcRequest | WebSocketRequest | Workspace, +): Promise { return await db.withAncestors(base || null, [ models.request.type, models.grpcRequest.type, diff --git a/packages/insomnia/src/common/select-file-or-folder.ts b/packages/insomnia/src/common/select-file-or-folder.ts index 86a225f056..46fb580967 100644 --- a/packages/insomnia/src/common/select-file-or-folder.ts +++ b/packages/insomnia/src/common/select-file-or-folder.ts @@ -40,10 +40,12 @@ export const selectFileOrFolder = async ({ itemTypes, extensions }: Options) => throw new Error(`unrecognized item type: "${type}"`); } }), - filters: [{ - extensions: (extensions?.length ? extensions : ['*']), - name: '', - }], + filters: [ + { + extensions: extensions?.length ? extensions : ['*'], + name: '', + }, + ], }); const fileSelection: FileSelection = { diff --git a/packages/insomnia/src/common/send-request.ts b/packages/insomnia/src/common/send-request.ts index c885bc6db7..8f201f534d 100644 --- a/packages/insomnia/src/common/send-request.ts +++ b/packages/insomnia/src/common/send-request.ts @@ -24,7 +24,10 @@ import { generateId } from './misc'; // The network layer uses settings from the settings model // We want to give consumers the ability to override certain settings type SettingsOverride = Pick; -const wrapAroundIterationOverIterationData = (list?: UserUploadEnvironment[], currentIteration?: number): UserUploadEnvironment | undefined => { +const wrapAroundIterationOverIterationData = ( + list?: UserUploadEnvironment[], + currentIteration?: number, +): UserUploadEnvironment | undefined => { if (currentIteration === undefined || !Array.isArray(list) || list.length === 0) { return undefined; } @@ -34,7 +37,14 @@ const wrapAroundIterationOverIterationData = (list?: UserUploadEnvironment[], cu return list[(currentIteration + 1) % list.length]; }; -export async function getSendRequestCallbackMemDb(environmentId: string, memDB: any, transientVariables: Environment, settingsOverrides?: SettingsOverride, iterationData?: UserUploadEnvironment[], iterationCount?: number) { +export async function getSendRequestCallbackMemDb( + environmentId: string, + memDB: any, + transientVariables: Environment, + settingsOverrides?: SettingsOverride, + iterationData?: UserUploadEnvironment[], + iterationCount?: number, +) { // Initialize the DB in-memory and fill it with data if we're given one await database.init( modelTypes(), @@ -42,7 +52,7 @@ export async function getSendRequestCallbackMemDb(environmentId: string, memDB: inMemoryOnly: true, }, true, - () => { }, + () => {}, ); const docs: BaseModel[] = []; @@ -89,11 +99,16 @@ export async function getSendRequestCallbackMemDb(environmentId: string, memDB: const workspaceMeta = await models.workspaceMeta.getByParentId(workspaceId); let activeGlobalEnvironment: Environment | undefined = undefined; if (workspaceMeta?.activeGlobalEnvironmentId) { - activeGlobalEnvironment = await models.environment.getById(workspaceMeta.activeGlobalEnvironmentId) || undefined; + activeGlobalEnvironment = + (await models.environment.getById(workspaceMeta.activeGlobalEnvironmentId)) || undefined; } const responseId = generateId('res'); - const responsesDir = path.join(process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : require('electron')).app.getPath('userData'), 'responses'); + const responsesDir = path.join( + process.env['INSOMNIA_DATA_PATH'] || + (process.type === 'renderer' ? window : require('electron')).app.getPath('userData'), + 'responses', + ); const timelinePath = path.join(responsesDir, responseId + '.timeline'); return { @@ -119,7 +134,13 @@ export async function getSendRequestCallbackMemDb(environmentId: string, memDB: const getCurrentRowOfIterationData = wrapAroundIterationOverIterationData(iterationData, iteration); await fs.mkdir(path.dirname(requestData.timelinePath), { recursive: true }); - const mutatedContext = await tryToExecutePreRequestScript(requestData, transientVariables, getCurrentRowOfIterationData, iteration, iterationCount); + const mutatedContext = await tryToExecutePreRequestScript( + requestData, + transientVariables, + getCurrentRowOfIterationData, + iteration, + iterationCount, + ); if (mutatedContext === null) { console.error('Time out while executing pre-request script'); return null; @@ -144,7 +165,7 @@ export async function getSendRequestCallbackMemDb(environmentId: string, memDB: requestData.caCert, mutatedContext.settings, requestData.timelinePath, - requestData.responseId + requestData.responseId, ); const res = await responseTransform(response, environmentId, renderedRequest, renderedResult.context); const postMutatedContext = await tryToExecuteAfterResponseScript({ @@ -156,19 +177,33 @@ export async function getSendRequestCallbackMemDb(environmentId: string, memDB: }); // TODO: figure out how to handle this error if ('error' in postMutatedContext) { - console.error('[network] An error occurred while running after-response script for request named:', renderedRequest.name); + console.error( + '[network] An error occurred while running after-response script for request named:', + renderedRequest.name, + ); throw { error: postMutatedContext.error, - response: await responseTransform(response, requestData.activeEnvironmentId, renderedRequest, renderedResult.context), + response: await responseTransform( + response, + requestData.activeEnvironmentId, + renderedRequest, + renderedResult.context, + ), }; } const { statusCode: status, statusMessage, headers: headerArray, elapsedTime: responseTime } = res; - const headers = headerArray?.reduce((acc, { name, value }) => ({ ...acc, [name.toLowerCase() || '']: value || '' }), []); - const bodyBuffer = await getBodyBuffer(res) as Buffer; + const headers = headerArray?.reduce( + (acc, { name, value }) => ({ ...acc, [name.toLowerCase() || '']: value || '' }), + [], + ); + const bodyBuffer = (await getBodyBuffer(res)) as Buffer; const data = bodyBuffer ? bodyBuffer.toString('utf8') : undefined; - const testResults = [...(mutatedContext.requestTestResults || []), ...(postMutatedContext.requestTestResults || [])]; + const testResults = [ + ...(mutatedContext.requestTestResults || []), + ...(postMutatedContext.requestTestResults || []), + ]; return { status, statusMessage, diff --git a/packages/insomnia/src/common/settings.ts b/packages/insomnia/src/common/settings.ts index ad1c9c0bc6..9788b6ecb6 100644 --- a/packages/insomnia/src/common/settings.ts +++ b/packages/insomnia/src/common/settings.ts @@ -82,9 +82,11 @@ export enum UpdateChannel { } /** Gets a subset of Settings where the values match a condition */ -export type SettingsOfType = NonNullable<{ - [Key in keyof Settings]: Settings[Key] extends MatchType ? Key : never; -}[keyof Settings]>; +export type SettingsOfType = NonNullable< + { + [Key in keyof Settings]: Settings[Key] extends MatchType ? Key : never; + }[keyof Settings] +>; export interface PluginConfig { disabled: boolean; diff --git a/packages/insomnia/src/common/strings.ts b/packages/insomnia/src/common/strings.ts index 5f45edb215..f8516085a8 100644 --- a/packages/insomnia/src/common/strings.ts +++ b/packages/insomnia/src/common/strings.ts @@ -12,8 +12,7 @@ type StringId = | 'defaultProject' | 'localProject' | 'remoteProject' - | 'environment' - ; + | 'environment'; export const strings: Record = { collection: { diff --git a/packages/insomnia/src/hidden-window-preload.ts b/packages/insomnia/src/hidden-window-preload.ts index db7649ec04..1f767bf7cb 100644 --- a/packages/insomnia/src/hidden-window-preload.ts +++ b/packages/insomnia/src/hidden-window-preload.ts @@ -2,7 +2,14 @@ import * as fs from 'node:fs'; import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'; -import { asyncTasksAllSettled, OriginalPromise, ProxiedPromise, type RequestContext, resetAsyncTasks, stopMonitorAsyncTasks } from '../../insomnia-scripting-environment/src/objects'; +import { + asyncTasksAllSettled, + OriginalPromise, + ProxiedPromise, + type RequestContext, + resetAsyncTasks, + stopMonitorAsyncTasks, +} from '../../insomnia-scripting-environment/src/objects'; import type { Compression } from './models/response'; // this will also import lots of node_modules into the preload script, consider moving this file insomnia-scripting-environment import { requireInterceptor } from './requireInterceptor'; @@ -11,7 +18,10 @@ export interface HiddenBrowserWindowToMainBridgeAPI { requireInterceptor: (module: string) => any; onmessage: (listener: (data: any, callback: (result: any) => void) => void) => void; curlRequest: (options: any) => Promise; - readCurlResponse: (options: { bodyPath: string; bodyCompression: Compression }) => Promise<{ body: string; error: string }>; + readCurlResponse: (options: { + bodyPath: string; + bodyCompression: Compression; + }) => Promise<{ body: string; error: string }>; setBusy: (busy: boolean) => void; appendFile: (logPath: string, logContent: string) => Promise; asyncTasksAllSettled: () => Promise; diff --git a/packages/insomnia/src/hidden-window.html b/packages/insomnia/src/hidden-window.html index 5634422d1a..f1c1172215 100644 --- a/packages/insomnia/src/hidden-window.html +++ b/packages/insomnia/src/hidden-window.html @@ -1,4 +1,4 @@ - + diff --git a/packages/insomnia/src/hidden-window.ts b/packages/insomnia/src/hidden-window.ts index 24037bac8e..4b6a631167 100644 --- a/packages/insomnia/src/hidden-window.ts +++ b/packages/insomnia/src/hidden-window.ts @@ -2,8 +2,19 @@ import * as Sentry from '@sentry/electron/renderer'; import { SENTRY_OPTIONS } from 'insomnia/src/common/sentry'; import * as _ from 'lodash'; -import { initInsomniaObject, InsomniaObject, waitForAllTestsDone } from '../../insomnia-scripting-environment/src/objects'; -import { getNewConsole, mergeClientCertificates, mergeCookieJar, mergeRequests, mergeSettings, type RequestContext } from '../../insomnia-scripting-environment/src/objects'; +import { + initInsomniaObject, + InsomniaObject, + waitForAllTestsDone, +} from '../../insomnia-scripting-environment/src/objects'; +import { + getNewConsole, + mergeClientCertificates, + mergeCookieJar, + mergeRequests, + mergeSettings, + type RequestContext, +} from '../../insomnia-scripting-environment/src/objects'; export interface HiddenBrowserWindowBridgeAPI { runScript: (options: { script: string; context: RequestContext }) => Promise; @@ -40,14 +51,12 @@ window.bridge.onmessage(async (data, callback) => { // This function is duplicated in scriptExecutor.ts to run in nodejs // TODO: consider removing this implementation and using only nodejs scripting -const runScript = async ( - { script, context }: { script: string; context: RequestContext }, -): Promise => { +const runScript = async ({ script, context }: { script: string; context: RequestContext }): Promise => { const scriptConsole = getNewConsole(); const executionContext = await initInsomniaObject(context, scriptConsole.log); - const AsyncFunction = (async () => { }).constructor; + const AsyncFunction = (async () => {}).constructor; const executeScript = AsyncFunction( 'insomnia', 'require', @@ -66,7 +75,7 @@ const runScript = async ( await waitForAllTestsDone(); window.bridge.stopMonitorAsyncTasks(); // the next one should not be monitored await window.bridge.asyncTasksAllSettled(); - return insomnia;` + return insomnia;`, ); const mutatedInsomniaObject = await executeScript( @@ -86,7 +95,10 @@ const runScript = async ( const mutatedContextObject = mutatedInsomniaObject.toObject(); const updatedRequest = mergeRequests(context.request, mutatedContextObject.request); const updatedSettings = mergeSettings(context.settings, mutatedContextObject.request); - const updatedCertificates = mergeClientCertificates(mutatedContextObject.clientCertificates, mutatedContextObject.request); + const updatedCertificates = mergeClientCertificates( + mutatedContextObject.clientCertificates, + mutatedContextObject.request, + ); const updatedCookieJar = mergeCookieJar(context.cookieJar, mutatedContextObject.cookieJar); return { @@ -101,10 +113,12 @@ const runScript = async ( name: context.baseEnvironment.name, data: mutatedContextObject.baseEnvironment, }, - iterationData: context.iterationData ? { - name: context.iterationData.name, - data: mutatedContextObject.iterationData, - } : undefined, + iterationData: context.iterationData + ? { + name: context.iterationData.name, + data: mutatedContextObject.iterationData, + } + : undefined, transientVariables: { name: context.transientVariables?.name || 'transientVariables', data: mutatedContextObject.variables, @@ -122,21 +136,15 @@ const runScript = async ( }; // proxiedSetTimeout has to be here as callback could be an async task -function proxiedSetTimeout( - callback: () => void, - ms?: number | undefined, -) { +function proxiedSetTimeout(callback: () => void, ms?: number | undefined) { let resolveHdl: (value: unknown) => void; new Promise(resolve => { resolveHdl = resolve; }); - return setTimeout( - () => { - callback(); - resolveHdl(null); - }, - ms, - ); + return setTimeout(() => { + callback(); + resolveHdl(null); + }, ms); } diff --git a/packages/insomnia/src/index.html b/packages/insomnia/src/index.html index cda03c76b3..f1f45cd2a2 100644 --- a/packages/insomnia/src/index.html +++ b/packages/insomnia/src/index.html @@ -1,5 +1,5 @@ - - + + - -
-
+ +
+
{ registerSecretStorageHandlers(); /** - * There's no option that prevents Electron from fetching spellcheck dictionaries from Chromium's CDN and passing a non-resolving URL is the only known way to prevent it from fetching. - * see: https://github.com/electron/electron/issues/22995 - * On macOS the OS spellchecker is used and therefore we do not download any dictionary files. - * This API is a no-op on macOS. - */ + * There's no option that prevents Electron from fetching spellcheck dictionaries from Chromium's CDN and passing a non-resolving URL is the only known way to prevent it from fetching. + * see: https://github.com/electron/electron/issues/22995 + * On macOS the OS spellchecker is used and therefore we do not download any dictionary files. + * This API is a no-op on macOS. + */ const disableSpellcheckerDownload = () => { - electron.session.defaultSession.setSpellCheckerDictionaryDownloadURL( - 'https://00.00/' - ); + electron.session.defaultSession.setSpellCheckerDictionaryDownloadURL('https://00.00/'); }; disableSpellcheckerDownload(); @@ -119,15 +119,21 @@ if (defaultProtocolSuccessful) { console.error(`[electron client protocol] FAILED to set default protocol '${fullDefaultProtocol}'`); const isDefaultAlready = app.isDefaultProtocolClient(defaultProtocol); if (isDefaultAlready) { - console.log(`[electron client protocol] the current executable is the default protocol for '${fullDefaultProtocol}'`); + console.log( + `[electron client protocol] the current executable is the default protocol for '${fullDefaultProtocol}'`, + ); } else { - console.log(`[electron client protocol] the current executable is not the default protocol for '${fullDefaultProtocol}'`); + console.log( + `[electron client protocol] the current executable is not the default protocol for '${fullDefaultProtocol}'`, + ); } // Note: `getApplicationInfoForProtocol` is not available on Linux, so we use `getApplicationNameForProtocol` instead const applicationName = app.getApplicationNameForProtocol(fullDefaultProtocol); if (applicationName) { - console.log(`[electron client protocol] the default application set for '${fullDefaultProtocol}' is '${applicationName}'`); + console.log( + `[electron client protocol] the default application set for '${fullDefaultProtocol}' is '${applicationName}'`, + ); } else { console.error(`[electron client protocol] the default application set for '${fullDefaultProtocol}' was not found`); } @@ -237,12 +243,22 @@ async function _createModelInstances() { const scratchPad = await models.workspace.getById(models.workspace.SCRATCHPAD_WORKSPACE_ID); if (!scratchpadProject) { console.log('[main] Initializing Scratch Pad Project'); - await models.project.create({ _id: models.project.SCRATCHPAD_PROJECT_ID, name: getProductName(), remoteId: null, parentId: models.organization.SCRATCHPAD_ORGANIZATION_ID }); + await models.project.create({ + _id: models.project.SCRATCHPAD_PROJECT_ID, + name: getProductName(), + remoteId: null, + parentId: models.organization.SCRATCHPAD_ORGANIZATION_ID, + }); } if (!scratchPad) { console.log('[main] Initializing Scratch Pad'); - await models.workspace.create({ _id: models.workspace.SCRATCHPAD_WORKSPACE_ID, name: 'Scratch Pad', parentId: models.project.SCRATCHPAD_PROJECT_ID, scope: 'collection' }); + await models.workspace.create({ + _id: models.workspace.SCRATCHPAD_WORKSPACE_ID, + name: 'Scratch Pad', + parentId: models.project.SCRATCHPAD_PROJECT_ID, + scope: 'collection', + }); } } catch (err) { console.warn('[main] Failed to create default project. It probably already exists', err); diff --git a/packages/insomnia/src/main/analytics.ts b/packages/insomnia/src/main/analytics.ts index eb54d02bb0..60933fb0cf 100644 --- a/packages/insomnia/src/main/analytics.ts +++ b/packages/insomnia/src/main/analytics.ts @@ -63,10 +63,7 @@ function hashString(input: string) { return crypto.createHash('sha256').update(input).digest('hex'); } -export async function trackSegmentEvent( - event: SegmentEvent, - properties?: Record, -) { +export async function trackSegmentEvent(event: SegmentEvent, properties?: Record) { const settings = await models.settings.getOrCreate(); const userSession = await models.userSession.getOrCreate(); if (!userSession?.hashedAccountId) { @@ -75,23 +72,26 @@ export async function trackSegmentEvent( const allowAnalytics = settings.enableAnalytics || userSession?.hashedAccountId; if (allowAnalytics) { try { - const anonymousId = await getDeviceId() ?? ''; + const anonymousId = (await getDeviceId()) ?? ''; const context = { app: { name: getProductName(), version: getAppVersion() }, os: { name: _getOsName(), version: process.getSystemVersion() }, }; - analytics.track({ - event, - properties, - context, - anonymousId, - userId: userSession?.hashedAccountId || '', - }, error => { - if (error) { - console.warn('[analytics] Error sending segment event', error); - } - }); + analytics.track( + { + event, + properties, + context, + anonymousId, + userId: userSession?.hashedAccountId || '', + }, + error => { + if (error) { + console.warn('[analytics] Error sending segment event', error); + } + }, + ); } catch (error: unknown) { console.warn('[analytics] Unexpected error while sending segment event', error); } @@ -108,7 +108,7 @@ export async function trackPageView(name: string) { const allowAnalytics = settings.enableAnalytics || userSession?.hashedAccountId; if (allowAnalytics) { try { - const anonymousId = await getDeviceId() ?? ''; + const anonymousId = (await getDeviceId()) ?? ''; const context = { app: { name: getProductName(), version: getAppVersion() }, os: { name: _getOsName(), version: process.getSystemVersion() }, diff --git a/packages/insomnia/src/main/api.protocol.ts b/packages/insomnia/src/main/api.protocol.ts index d1265e0152..8e93c8a958 100644 --- a/packages/insomnia/src/main/api.protocol.ts +++ b/packages/insomnia/src/main/api.protocol.ts @@ -13,19 +13,24 @@ const httpScheme = 'http'; const templatingWorkerDatabaseInterface = 'insomnia-templating-worker-database'; export async function registerInsomniaProtocols() { - protocol.registerSchemesAsPrivileged([{ - scheme: insomniaStreamScheme, - privileges: { secure: true, standard: true, supportFetchAPI: true }, - }, { - scheme: httpsScheme, + protocol.registerSchemesAsPrivileged([ + { + scheme: insomniaStreamScheme, privileges: { secure: true, standard: true, supportFetchAPI: true }, - }, { - scheme: httpScheme, + }, + { + scheme: httpsScheme, privileges: { secure: true, standard: true, supportFetchAPI: true }, - }, { - scheme: templatingWorkerDatabaseInterface, + }, + { + scheme: httpScheme, privileges: { secure: true, standard: true, supportFetchAPI: true }, - }]); + }, + { + scheme: templatingWorkerDatabaseInterface, + privileges: { secure: true, standard: true, supportFetchAPI: true }, + }, + ]); await app.whenReady(); diff --git a/packages/insomnia/src/main/authorizeUserInWindow.ts b/packages/insomnia/src/main/authorizeUserInWindow.ts index 8c76cb7bbc..79ffe085a5 100644 --- a/packages/insomnia/src/main/authorizeUserInWindow.ts +++ b/packages/insomnia/src/main/authorizeUserInWindow.ts @@ -4,7 +4,7 @@ import * as models from '../models'; export enum ChromiumVerificationResult { BLIND_TRUST = 0, - USE_CHROMIUM_RESULT = -3 + USE_CHROMIUM_RESULT = -3, } export enum URLLoadErrorCodes { @@ -14,7 +14,7 @@ export enum URLLoadErrorCodes { * - https://www.electronjs.org/docs/latest/api/web-contents#event-did-fail-load * - https://source.chromium.org/chromium/chromium/src/+/main:net/base/net_error_list.h */ - ERR_ABORTED = -3 + ERR_ABORTED = -3, } export function authorizeUserInWindow({ @@ -32,13 +32,7 @@ export function authorizeUserInWindow({ let finalUrl: string | null = null; // Fetch user setting to determine whether to validate SSL certificates during auth - const { - validateAuthSSL, - proxyEnabled, - httpProxy, - httpsProxy, - noProxy, - } = await models.settings.get(); + const { validateAuthSSL, proxyEnabled, httpProxy, httpsProxy, noProxy } = await models.settings.get(); // Create a child window const child = new BrowserWindow({ @@ -51,18 +45,20 @@ export function authorizeUserInWindow({ function _parseUrl(currentUrl: string, source: string) { if (currentUrl.match(urlSuccessRegex)) { - console.log(`[oauth2] ${source}: Matched success redirect to "${currentUrl}" with ${urlSuccessRegex.toString()}`,); + console.log( + `[oauth2] ${source}: Matched success redirect to "${currentUrl}" with ${urlSuccessRegex.toString()}`, + ); finalUrl = currentUrl; child.close(); } else if (currentUrl.match(urlFailureRegex)) { - console.log(`[oauth2] ${source}: Matched error redirect to "${currentUrl}" with ${urlFailureRegex.toString()}`,); + console.log(`[oauth2] ${source}: Matched error redirect to "${currentUrl}" with ${urlFailureRegex.toString()}`); finalUrl = currentUrl; child.close(); } else if (currentUrl === url) { // It's the first one, so it's not a redirect console.log(`[oauth2] ${source}: Loaded "${currentUrl}"`); } else { - console.log(`[oauth2] ${source}: Ignoring URL "${currentUrl}". Didn't match ${urlSuccessRegex.toString()}`,); + console.log(`[oauth2] ${source}: Ignoring URL "${currentUrl}". Didn't match ${urlSuccessRegex.toString()}`); } } @@ -93,23 +89,26 @@ export function authorizeUserInWindow({ const cancelId = buttonLabels.length; // Prompt the user to select a certificate to use. - dialog.showMessageBox(child, { - type: 'none', - buttons: [...buttonLabels, 'Cancel'], - cancelId: cancelId, - message: `The website\n"${url}"\nrequires a client certificate.`, - detail: 'This website requires a certificate to validate your identity. Select the certificate to use when you connect to this website.', - textWidth: 300, - }).then(r => { - const selectedButtonIndex = r.response; - // Cancel button clicked - if (r.response === cancelId) { - child.close(); - return; - } - const selectedCertificate = certificateList[selectedButtonIndex]; - callback(selectedCertificate); - }); + dialog + .showMessageBox(child, { + type: 'none', + buttons: [...buttonLabels, 'Cancel'], + cancelId: cancelId, + message: `The website\n"${url}"\nrequires a client certificate.`, + detail: + 'This website requires a certificate to validate your identity. Select the certificate to use when you connect to this website.', + textWidth: 300, + }) + .then(r => { + const selectedButtonIndex = r.response; + // Cancel button clicked + if (r.response === cancelId) { + child.close(); + return; + } + const selectedCertificate = certificateList[selectedButtonIndex]; + callback(selectedCertificate); + }); }); // Catch the redirect after login @@ -144,9 +143,7 @@ export function authorizeUserInWindow({ // Set proxy for browser window if (proxyEnabled) { await child.webContents.session.setProxy({ - proxyRules: - (httpProxy ? `http=${httpProxy};` : '') + - (httpsProxy ? `https=${httpsProxy}` : ''), + proxyRules: (httpProxy ? `http=${httpProxy};` : '') + (httpsProxy ? `https=${httpsProxy}` : ''), proxyBypassRules: noProxy, }); console.log('[oauth2] Proxy loaded'); diff --git a/packages/insomnia/src/main/backup.ts b/packages/insomnia/src/main/backup.ts index 2b6bf3f112..f181e1b828 100644 --- a/packages/insomnia/src/main/backup.ts +++ b/packages/insomnia/src/main/backup.ts @@ -12,12 +12,15 @@ export async function backupIfNewerVersionAvailable() { try { const settings = await models.settings.get(); console.log('[main] Checking for newer version than ', version); - const response = await electron.net.fetch(`${getUpdatesBaseURL()}/builds/check/mac?v=${version}&app=${appConfig.appId}&channel=${settings.updateChannel}`, { - method: 'GET', - headers: new Headers({ - 'X-Insomnia-Client': getClientString(), - }), - }); + const response = await electron.net.fetch( + `${getUpdatesBaseURL()}/builds/check/mac?v=${version}&app=${appConfig.appId}&channel=${settings.updateChannel}`, + { + method: 'GET', + headers: new Headers({ + 'X-Insomnia-Client': getClientString(), + }), + }, + ); if (response) { console.log('[main] Found newer version'); backup(); diff --git a/packages/insomnia/src/main/git-service.ts b/packages/insomnia/src/main/git-service.ts index 61594c9ee3..83d9b61c87 100644 --- a/packages/insomnia/src/main/git-service.ts +++ b/packages/insomnia/src/main/git-service.ts @@ -7,7 +7,16 @@ import path from 'path'; import { v4 } from 'uuid'; import YAML, { parse } from 'yaml'; -import { getApiBaseURL, getAppWebsiteBaseURL, getGitHubGraphQLApiURL, getGitHubRestApiUrl, INSOMNIA_GITLAB_API_URL, INSOMNIA_GITLAB_CLIENT_ID, INSOMNIA_GITLAB_REDIRECT_URI, PLAYWRIGHT } from '../common/constants'; +import { + getApiBaseURL, + getAppWebsiteBaseURL, + getGitHubGraphQLApiURL, + getGitHubRestApiUrl, + INSOMNIA_GITLAB_API_URL, + INSOMNIA_GITLAB_CLIENT_ID, + INSOMNIA_GITLAB_REDIRECT_URI, + PLAYWRIGHT, +} from '../common/constants'; import { database } from '../common/database'; import { insomniaFileSchema } from '../common/import-v5-parser'; import { insomniaSchemaTypeToScope } from '../common/insomnia-v5'; @@ -15,7 +24,13 @@ import * as models from '../models'; import type { GitRepository } from '../models/git-repository'; import { type WorkspaceScope, WorkspaceScopeKeys } from '../models/workspace'; import { fsClient } from '../sync/git/fs-client'; -import GitVCS, { GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME, GIT_INTERNAL_DIR, MergeConflictError } from '../sync/git/git-vcs'; +import GitVCS, { + GIT_CLONE_DIR, + GIT_INSOMNIA_DIR, + GIT_INSOMNIA_DIR_NAME, + GIT_INTERNAL_DIR, + MergeConflictError, +} from '../sync/git/git-vcs'; import { MemClient } from '../sync/git/mem-client'; import { NeDBClient } from '../sync/git/ne-db-client'; import { GitProjectNeDBClient } from '../sync/git/project-ne-db-client'; @@ -29,16 +44,25 @@ import { SegmentEvent, trackSegmentEvent } from './analytics'; import { ipcMainHandle } from './ipc/electron'; type PushPull = 'push' | 'pull'; -type VCSAction = PushPull | `force_${PushPull}` | - 'create_branch' | 'merge_branch' | 'delete_branch' | 'checkout_branch' | - 'commit' | 'stage_all' | 'stage' | 'unstage_all' | 'unstage' | 'rollback' | 'rollback_all' | - 'update' | 'setup' | 'clone'; +type VCSAction = + | PushPull + | `force_${PushPull}` + | 'create_branch' + | 'merge_branch' + | 'delete_branch' + | 'checkout_branch' + | 'commit' + | 'stage_all' + | 'stage' + | 'unstage_all' + | 'unstage' + | 'rollback' + | 'rollback_all' + | 'update' + | 'setup' + | 'clone'; -export function vcsSegmentEventProperties( - type: 'git', - action: VCSAction, - error?: string -) { +export function vcsSegmentEventProperties(type: 'git', action: VCSAction, error?: string) { return { type, action, error }; } @@ -73,9 +97,7 @@ async function getGitRepository({ projectId, workspaceId }: { projectId: string; throw new Error('Workspace is not linked to a git repository'); } - const gitRepository = await models.gitRepository.getById( - workspaceMeta.gitRepositoryId - ); + const gitRepository = await models.gitRepository.getById(workspaceMeta.gitRepositoryId); invariant(gitRepository, 'Git Repository not found'); return gitRepository; @@ -85,9 +107,7 @@ async function getGitRepository({ projectId, workspaceId }: { projectId: string; const project = await models.project.getById(projectId); invariant(project, 'Project not found'); invariant(project.gitRepositoryId, 'Project is not linked to a git repository'); - const gitRepository = await models.gitRepository.getById( - project.gitRepositoryId - ); + const gitRepository = await models.gitRepository.getById(project.gitRepositoryId); invariant(gitRepository, 'Git Repository not found'); return gitRepository; } @@ -103,7 +123,7 @@ async function getGitFSClient({ }) { const baseDir = path.join( process.env['INSOMNIA_DATA_PATH'] || app.getPath('userData'), - `version-control/git/${gitRepositoryId}` + `version-control/git/${gitRepositoryId}`, ); // Workspace FS Client @@ -144,13 +164,7 @@ async function getGitFSClient({ return routableFS; } -export async function loadGitRepository({ - projectId, - workspaceId, -}: { - projectId: string; - workspaceId?: string; -}) { +export async function loadGitRepository({ projectId, workspaceId }: { projectId: string; workspaceId?: string }) { try { const gitRepository = await getGitRepository({ workspaceId, projectId }); @@ -201,8 +215,7 @@ export async function loadGitRepository({ gitRepository, }; } catch (e) { - const errorMessage = - e instanceof Error ? e.message : 'Error while fetching git repository.'; + const errorMessage = e instanceof Error ? e.message : 'Error while fetching git repository.'; return { errors: [errorMessage], }; @@ -211,14 +224,17 @@ export async function loadGitRepository({ export type GitBranchesLoaderData = | { - branches: string[]; - remoteBranches: string[]; - } + branches: string[]; + remoteBranches: string[]; + } | { - errors: string[]; - }; + errors: string[]; + }; -export const getGitBranches = async ({ projectId, workspaceId }: { +export const getGitBranches = async ({ + projectId, + workspaceId, +}: { projectId: string; workspaceId?: string; }): Promise => { @@ -239,10 +255,7 @@ export const getGitBranches = async ({ projectId, workspaceId }: { } }; -export const gitFetchAction = async ({ projectId, workspaceId }: { - projectId: string; - workspaceId?: string; -}) => { +export const gitFetchAction = async ({ projectId, workspaceId }: { projectId: string; workspaceId?: string }) => { try { const gitRepository = await getGitRepository({ projectId, workspaceId }); await GitVCS.fetch({ @@ -262,10 +275,7 @@ export const gitFetchAction = async ({ projectId, workspaceId }: { } }; -export const gitLogLoader = async ({ projectId, workspaceId }: { - projectId: string; - workspaceId?: string; -}) => { +export const gitLogLoader = async ({ projectId, workspaceId }: { projectId: string; workspaceId?: string }) => { try { await getGitRepository({ projectId, workspaceId }); const log = await GitVCS.log({ depth: 35 }); @@ -334,7 +344,10 @@ export interface GitCanPushLoaderData { canPush: boolean; } -export const canPushLoader = async ({ projectId, workspaceId }: { +export const canPushLoader = async ({ + projectId, + workspaceId, +}: { projectId: string; workspaceId?: string; }): Promise => { @@ -364,7 +377,11 @@ async function isInsomniaFile(fullPath: string, fsClient: PromiseFsClient) { // Recursively finds all .yaml files in a repository that are Insomnia files and returns their paths relative to the repo root. // Insomnia files are defined as files that contain the string 'insomnia.rest' in the first line. -const recursivelyFindInsomniaFiles = async (fsClient: PromiseFsClient, dir: string, files: string[] = []): Promise => { +const recursivelyFindInsomniaFiles = async ( + fsClient: PromiseFsClient, + dir: string, + files: string[] = [], +): Promise => { const dirFiles = await fsClient.promises.readdir(dir); for (const file of dirFiles) { const repoRelativePath = path.join(dir, file); @@ -374,7 +391,7 @@ const recursivelyFindInsomniaFiles = async (fsClient: PromiseFsClient, dir: stri await recursivelyFindInsomniaFiles(fsClient, repoRelativePath, files); } - if (!isDirectory && await isInsomniaFile(repoRelativePath, fsClient)) { + if (!isDirectory && (await isInsomniaFile(repoRelativePath, fsClient))) { files.push(repoRelativePath); } } @@ -398,15 +415,18 @@ export const initGitRepoCloneAction = async ({ token: string; username: string; oauth2format?: string; -}): Promise<{ - files: { - scope: WorkspaceScope; - name: string; - path: string; - }[]; -} | { - errors: string[]; -}> => { +}): Promise< + | { + files: { + scope: WorkspaceScope; + name: string; + path: string; + }[]; + } + | { + errors: string[]; + } +> => { const repoSettingsPatch: Partial = {}; repoSettingsPatch.uri = parseGitToHttpsURL(uri); repoSettingsPatch.author = { @@ -416,10 +436,7 @@ export const initGitRepoCloneAction = async ({ // Git Credentials if (oauth2format) { - invariant( - oauth2format === 'gitlab' || oauth2format === 'github', - 'OAuth2 format is required' - ); + invariant(oauth2format === 'gitlab' || oauth2format === 'github', 'OAuth2 format is required'); repoSettingsPatch.credentials = { username, @@ -472,7 +489,7 @@ export const initGitRepoCloneAction = async ({ name: insomniaFile.name || 'Untitled', path: file, }; - }) + }), ); return { files }; @@ -512,10 +529,7 @@ export const cloneGitRepoAction = async ({ // Git Credentials if (oauth2format) { - invariant( - oauth2format === 'gitlab' || oauth2format === 'github', - 'OAuth2 format is required' - ); + invariant(oauth2format === 'gitlab' || oauth2format === 'github', 'OAuth2 format is required'); repoSettingsPatch.credentials = { username, @@ -532,10 +546,7 @@ export const cloneGitRepoAction = async ({ }; } - trackSegmentEvent( - SegmentEvent.vcsSyncStart, - vcsSegmentEventProperties('git', 'clone'), - ); + trackSegmentEvent(SegmentEvent.vcsSyncStart, vcsSegmentEventProperties('git', 'clone')); repoSettingsPatch.needsFullClone = true; const inMemoryFsClient = MemClient.createClient(); @@ -659,10 +670,7 @@ export const cloneGitRepoAction = async ({ // Git Credentials if (oauth2format) { - invariant( - oauth2format === 'gitlab' || oauth2format === 'github', - 'OAuth2 format is required' - ); + invariant(oauth2format === 'gitlab' || oauth2format === 'github', 'OAuth2 format is required'); repoSettingsPatch.credentials = { username, @@ -679,10 +687,7 @@ export const cloneGitRepoAction = async ({ }; } - trackSegmentEvent( - SegmentEvent.vcsSyncStart, - vcsSegmentEventProperties('git', 'clone'), - ); + trackSegmentEvent(SegmentEvent.vcsSyncStart, vcsSegmentEventProperties('git', 'clone')); repoSettingsPatch.needsFullClone = true; const inMemoryFsClient = MemClient.createClient(); @@ -707,23 +712,17 @@ export const cloneGitRepoAction = async ({ }; } - const containsInsomniaDir = async ( - fsClient: Record - ): Promise => { + const containsInsomniaDir = async (fsClient: Record): Promise => { const rootDirs: string[] = await fsClient.promises.readdir(GIT_CLONE_DIR); return rootDirs.includes(GIT_INSOMNIA_DIR_NAME); }; - const containsInsomniaWorkspaceDir = async ( - fsClient: Record - ): Promise => { + const containsInsomniaWorkspaceDir = async (fsClient: Record): Promise => { if (!(await containsInsomniaDir(fsClient))) { return false; } - const rootDirs: string[] = await fsClient.promises.readdir( - GIT_INSOMNIA_DIR - ); + const rootDirs: string[] = await fsClient.promises.readdir(GIT_INSOMNIA_DIR); return rootDirs.includes(models.workspace.type); }; @@ -743,13 +742,10 @@ export const cloneGitRepoAction = async ({ }); await models.apiSpec.getOrCreateForParentId(workspace._id); - trackSegmentEvent( - SegmentEvent.vcsSyncComplete, - { - ...vcsSegmentEventProperties('git', 'clone', 'no directory found'), - providerName, - }, - ); + trackSegmentEvent(SegmentEvent.vcsSyncComplete, { + ...vcsSegmentEventProperties('git', 'clone', 'no directory found'), + providerName, + }); workspaceId = workspace._id; @@ -764,12 +760,10 @@ export const cloneGitRepoAction = async ({ const workspaces = await inMemoryFsClient.promises.readdir(workspaceBase); if (workspaces.length === 0) { - trackSegmentEvent( - SegmentEvent.vcsSyncComplete, { + trackSegmentEvent(SegmentEvent.vcsSyncComplete, { ...vcsSegmentEventProperties('git', 'clone', 'no workspaces found'), providerName, - }, - ); + }); return { errors: ['No workspaces found in repository'], @@ -777,17 +771,10 @@ export const cloneGitRepoAction = async ({ } if (workspaces.length > 1) { - trackSegmentEvent( - SegmentEvent.vcsSyncComplete, - { - ...vcsSegmentEventProperties( - 'git', - 'clone', - 'multiple workspaces found' - ), - providerName, - }, - ); + trackSegmentEvent(SegmentEvent.vcsSyncComplete, { + ...vcsSegmentEventProperties('git', 'clone', 'multiple workspaces found'), + providerName, + }); return { errors: ['Multiple workspaces found in repository. Expected one.'], @@ -799,7 +786,8 @@ export const cloneGitRepoAction = async ({ const workspaceJson = await inMemoryFsClient.promises.readFile(workspacePath); const workspace = YAML.parse(workspaceJson.toString()); workspaceId = workspace._id; - scope = (workspace.scope === WorkspaceScopeKeys.collection) ? WorkspaceScopeKeys.collection : WorkspaceScopeKeys.design; + scope = + workspace.scope === WorkspaceScopeKeys.collection ? WorkspaceScopeKeys.collection : WorkspaceScopeKeys.design; // Check if the workspace already exists const existingWorkspace = await models.workspace.getById(workspace._id); @@ -807,7 +795,9 @@ export const cloneGitRepoAction = async ({ const project = await models.project.getById(existingWorkspace.parentId); if (!project) { return { - errors: ['It seems that the repository being cloned is connected to an orphaned workspace. Please move that workspace to a project and try again.'], + errors: [ + 'It seems that the repository being cloned is connected to an orphaned workspace. Please move that workspace to a project and try again.', + ], }; } @@ -905,7 +895,6 @@ export const updateGitRepoAction = async ({ token: string; }) => { try { - let gitRepositoryId: string | null | undefined = null; if (workspaceId) { @@ -933,10 +922,7 @@ export const updateGitRepoAction = async ({ // Git Credentials if (oauth2format) { - invariant( - oauth2format === 'gitlab' || oauth2format === 'github', - 'OAuth2 format is required' - ); + invariant(oauth2format === 'gitlab' || oauth2format === 'github', 'OAuth2 format is required'); repoSettingsPatch.credentials = { username, @@ -1008,10 +994,7 @@ export const updateGitRepoAction = async ({ } }; -export const resetGitRepoAction = async ({ projectId, workspaceId }: { - projectId: string; - workspaceId?: string; -}) => { +export const resetGitRepoAction = async ({ projectId, workspaceId }: { projectId: string; workspaceId?: string }) => { const repo = await getGitRepository({ projectId, workspaceId }); invariant(repo, 'Git Repository not found'); @@ -1068,8 +1051,7 @@ export const commitToGitRepoAction = async ({ hasUnpushedChanges, }); } catch (e) { - const message = - e instanceof Error ? e.message : 'Error while committing changes'; + const message = e instanceof Error ? e.message : 'Error while committing changes'; return { errors: [message] }; } @@ -1093,14 +1075,12 @@ export const commitAndPushToGitRepoAction = async ({ const providerName = getOauth2FormatName(repo?.credentials); - trackSegmentEvent( - SegmentEvent.vcsAction, { + trackSegmentEvent(SegmentEvent.vcsAction, { ...vcsSegmentEventProperties('git', 'commit'), providerName, }); } catch (e) { - const message = - e instanceof Error ? e.message : 'Error while committing changes'; + const message = e instanceof Error ? e.message : 'Error while committing changes'; return { errors: [message] }; } @@ -1142,13 +1122,17 @@ export const commitAndPushToGitRepoAction = async ({ } catch (err: unknown) { if (err instanceof Errors.PushRejectedError && err.data.reason === 'not-fast-forward') { return { - errors: ['Push Rejected. It seems that the remote repository has changes that you do not have locally. Please pull the changes and try again.'], + errors: [ + 'Push Rejected. It seems that the remote repository has changes that you do not have locally. Please pull the changes and try again.', + ], }; } if (err instanceof Errors.PushRejectedError && err.data.reason === 'tag-exists') { return { - errors: ['Push Rejected. It seems that the tag you are trying to push already exists in the remote repository.'], + errors: [ + 'Push Rejected. It seems that the tag you are trying to push already exists in the remote repository.', + ], }; } @@ -1213,10 +1197,7 @@ export const createNewGitBranchAction = async ({ errors: [`${err.message}, ${err.data.response}`], }; } - const errorMessage = - err instanceof Error - ? err.message - : 'Something went wrong while creating a new branch'; + const errorMessage = err instanceof Error ? err.message : 'Something went wrong while creating a new branch'; return { errors: [errorMessage], }; @@ -1320,10 +1301,7 @@ export const mergeGitBranch = async ({ errorMessage = `${err.message}, ${err.data.response}`; } - trackSegmentEvent( - SegmentEvent.vcsAction, - vcsSegmentEventProperties('git', 'merge_branch', errorMessage), - ); + trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'merge_branch', errorMessage)); return { errors: [errorMessage], @@ -1416,14 +1394,18 @@ export const pushToGitRemoteAction = async ({ } catch (err: unknown) { if (err instanceof Errors.PushRejectedError && err.data.reason === 'not-fast-forward') { return { - errors: ['Push Rejected. It seems that the remote repository has changes that you do not have locally. Please pull the changes and try again.'], + errors: [ + 'Push Rejected. It seems that the remote repository has changes that you do not have locally. Please pull the changes and try again.', + ], gitRepository, }; } if (err instanceof Errors.PushRejectedError && err.data.reason === 'tag-exists') { return { - errors: ['Push Rejected. It seems that the tag you are trying to push already exists in the remote repository.'], + errors: [ + 'Push Rejected. It seems that the tag you are trying to push already exists in the remote repository.', + ], }; } @@ -1449,13 +1431,7 @@ export const pushToGitRemoteAction = async ({ return {}; }; -export async function pullFromGitRemote({ - projectId, - workspaceId, -}: { - projectId: string; - workspaceId?: string; -}) { +export async function pullFromGitRemote({ projectId, workspaceId }: { projectId: string; workspaceId?: string }) { try { const gitRepository = await getGitRepository({ projectId, workspaceId }); const providerName = getOauth2FormatName(gitRepository.credentials); @@ -1478,10 +1454,7 @@ export async function pullFromGitRemote({ if (err instanceof Errors.HttpError) { errorMessage = `${err.message}, ${err.data.response}`; } - trackSegmentEvent( - SegmentEvent.vcsAction, - vcsSegmentEventProperties('git', 'pull', errorMessage), - ); + trackSegmentEvent(SegmentEvent.vcsAction, vcsSegmentEventProperties('git', 'pull', errorMessage)); return { errors: [errorMessage], @@ -1489,21 +1462,19 @@ export async function pullFromGitRemote({ } } -export const continueMerge = async ( - { - projectId, - workspaceId, - handledMergeConflicts, - commitMessage, - commitParent, - }: { - projectId: string; - workspaceId?: string; - handledMergeConflicts: MergeConflict[]; - commitMessage: string; - commitParent: string[]; - } -) => { +export const continueMerge = async ({ + projectId, + workspaceId, + handledMergeConflicts, + commitMessage, + commitParent, +}: { + projectId: string; + workspaceId?: string; + handledMergeConflicts: MergeConflict[]; + commitMessage: string; + commitParent: string[]; +}) => { try { await getGitRepository({ workspaceId, projectId }); const bufferId = await database.bufferChanges(); @@ -1557,14 +1528,12 @@ export const discardChangesAction = async ({ await getGitRepository({ workspaceId, projectId }); const { changes } = await getGitChanges(GitVCS); - const files = changes.unstaged - .filter(change => paths.includes(change.path)); + const files = changes.unstaged.filter(change => paths.includes(change.path)); await GitVCS.discardChanges(files); return {}; } catch (e) { - const errorMessage = - e instanceof Error ? e.message : 'Error while rolling back changes'; + const errorMessage = e instanceof Error ? e.message : 'Error while rolling back changes'; return { errors: [errorMessage], }; @@ -1623,14 +1592,12 @@ export const stageChangesAction = async ({ await getGitRepository({ workspaceId, projectId }); const { changes } = await getGitChanges(GitVCS); - const files = changes.unstaged - .filter(change => paths.includes(change.path)); + const files = changes.unstaged.filter(change => paths.includes(change.path)); await GitVCS.stageChanges(files); return {}; } catch (e) { - const errorMessage = - e instanceof Error ? e.message : 'Error while staging changes'; + const errorMessage = e instanceof Error ? e.message : 'Error while staging changes'; return { errors: [errorMessage], }; @@ -1652,32 +1619,26 @@ export const unstageChangesAction = async ({ await getGitRepository({ workspaceId, projectId }); const { changes } = await getGitChanges(GitVCS); - const files = changes.staged - .filter(change => paths.includes(change.path)); + const files = changes.staged.filter(change => paths.includes(change.path)); await GitVCS.unstageChanges(files); return {}; } catch (e) { - const errorMessage = - e instanceof Error ? e.message : 'Error while unstaging changes'; + const errorMessage = e instanceof Error ? e.message : 'Error while unstaging changes'; return { errors: [errorMessage], }; } - }; -function getPreviewItemName(previewDiffItem: { - before: string; - after: string; -}) { +function getPreviewItemName(previewDiffItem: { before: string; after: string }) { let prevName = ''; let nextName = ''; try { const prev = parse(previewDiffItem.before); - if (prev && 'fileName' in prev || 'name' in prev) { + if ((prev && 'fileName' in prev) || 'name' in prev) { prevName = prev.fileName || prev.name; } } catch (e) { @@ -1686,7 +1647,7 @@ function getPreviewItemName(previewDiffItem: { try { const next = parse(previewDiffItem.after); - if (next && 'fileName' in next || 'name' in next) { + if ((next && 'fileName' in next) || 'name' in next) { nextName = next.fileName || next.name; } } catch (e) { @@ -1696,15 +1657,17 @@ function getPreviewItemName(previewDiffItem: { return nextName || prevName; } -export type GitDiffResult = { - name: string; - diff?: { - before: string; - after: string; - }; -} | { - errors: string[]; -}; +export type GitDiffResult = + | { + name: string; + diff?: { + before: string; + after: string; + }; + } + | { + errors: string[]; + }; export const diffFileLoader = async ({ projectId, @@ -1721,21 +1684,22 @@ export const diffFileLoader = async ({ await getGitRepository({ workspaceId, projectId }); const fileStatus = await GitVCS.fileStatus(filepath); - const diff = staged ? { - before: fileStatus.head, - after: fileStatus.stage, - } : { - before: fileStatus.stage || fileStatus.head, - after: fileStatus.workdir, - }; + const diff = staged + ? { + before: fileStatus.head, + after: fileStatus.stage, + } + : { + before: fileStatus.stage || fileStatus.head, + after: fileStatus.workdir, + }; return { name: getPreviewItemName(diff) || filepath, diff, }; } catch (e) { - const errorMessage = - e instanceof Error ? e.message : 'Error while unstaging changes'; + const errorMessage = e instanceof Error ? e.message : 'Error while unstaging changes'; return { errors: [errorMessage], }; @@ -1755,14 +1719,21 @@ interface GitRepoDirectory { children: (GitRepoDirectory | GitRepoFile)[]; } -type FileTree = { - id: string; - name: string; - type: 'root'; - children: (GitRepoDirectory | GitRepoFile)[]; -} | GitRepoDirectory | GitRepoFile; +type FileTree = + | { + id: string; + name: string; + type: 'root'; + children: (GitRepoDirectory | GitRepoFile)[]; + } + | GitRepoDirectory + | GitRepoFile; -const getRepositoryDirectoryTree = async ({ projectId }: { projectId: string }): Promise<{ +const getRepositoryDirectoryTree = async ({ + projectId, +}: { + projectId: string; +}): Promise<{ repositoryTree: FileTree; folderList: Record; }> => { @@ -1843,17 +1814,9 @@ interface GitHubUserApiResponse { url: string; } -async function completeSignInToGitHub({ - code, - state, -}: { - code: string; - state: string; -}) { +async function completeSignInToGitHub({ code, state }: { code: string; state: string }) { if (!PLAYWRIGHT && !statesCache.has(state)) { - throw new Error( - 'Invalid state parameter. It looks like the authorization flow was not initiated by the app.' - ); + throw new Error('Invalid state parameter. It looks like the authorization flow was not initiated by the app.'); } const response = await net.fetch(getApiBaseURL() + '/v1/oauth/github-app', { @@ -1866,7 +1829,7 @@ async function completeSignInToGitHub({ }, }); - const data = await response.json() as { access_token: string }; + const data = (await response.json()) as { access_token: string }; statesCache.delete(state); const existingGitHubCredentials = await models.gitCredentials.getByProvider('github'); @@ -1886,10 +1849,7 @@ async function completeSignInToGitHub({ }, }).then(response => response.json() as Promise); - const [emails, user] = await Promise.all([ - emailsPromise, - userPromise, - ]); + const [emails, user] = await Promise.all([emailsPromise, userPromise]); const userProfileEmail = user.email ?? ''; const email = emails.find(e => e.primary)?.email ?? userProfileEmail ?? ''; @@ -1939,10 +1899,13 @@ type GitHubRepositoriesApiResponse = GitHubRepositoryApiResponse[]; const GITHUB_USER_REPOS_URL = `${getGitHubRestApiUrl()}/user/repos`; -async function getGitHubRepositories( - { url = `${GITHUB_USER_REPOS_URL}?per_page=100`, repos = [] }: - { url?: string; repos?: GitHubRepositoriesApiResponse } -) { +async function getGitHubRepositories({ + url = `${GITHUB_USER_REPOS_URL}?per_page=100`, + repos = [], +}: { + url?: string; + repos?: GitHubRepositoriesApiResponse; +}) { const credentials = await models.gitCredentials.getByProvider('github'); const opts = { headers: { @@ -1954,7 +1917,6 @@ async function getGitHubRepositories( if (!response.ok) { const raw = await response.text(); if (response.status === 401) { - return { errors: [`User token not authorized to fetch repositories, please sign out and back in.\nResponse: ${raw}`], repos: [], @@ -1979,7 +1941,11 @@ async function getGitHubRepositories( const lastPage = lastUrl.searchParams.get('page'); if (lastPage) { const pages = Number(lastPage); - const pageList = await Promise.all(Array.from({ length: pages - 1 }, (_, i) => fetch(`${GITHUB_USER_REPOS_URL}?per_page=100&page=${i + 2}`, opts))); + const pageList = await Promise.all( + Array.from({ length: pages - 1 }, (_, i) => + fetch(`${GITHUB_USER_REPOS_URL}?per_page=100&page=${i + 2}`, opts), + ), + ); for (const page of pageList) { const pageData = await page.json(); pullableRepos = pageData.filter((repo: GitHubRepositoryApiResponse) => repo.permissions.pull); @@ -2017,7 +1983,7 @@ async function getGitHubRepository({ uri }: { uri: string }) { }; } - return { repo: await response.json() as GitHubRepositoryApiResponse, errors: [], notFound: false }; + return { repo: (await response.json()) as GitHubRepositoryApiResponse, errors: [], notFound: false }; } /** @@ -2047,7 +2013,10 @@ const getGitLabConfig = async () => { method: 'GET', }); - const { applicationId: clientId, redirectUri } = await configResponse.json() as { applicationId: string; redirectUri: string }; + const { applicationId: clientId, redirectUri } = (await configResponse.json()) as { + applicationId: string; + redirectUri: string; + }; return { clientId, @@ -2056,11 +2025,7 @@ const getGitLabConfig = async () => { }; function base64URLEncode(buffer: Buffer) { - return buffer - .toString('base64') - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=/g, ''); + return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } export const getGitLabOauthApiURL = () => INSOMNIA_GITLAB_API_URL || 'https://gitlab.com'; @@ -2101,22 +2066,14 @@ async function initSignInToGitLab() { await shell.openExternal(gitlabURL.toString()); } -async function completeSignInToGitLab({ - code, - state, -}: { - code: string; - state: string; -}) { +async function completeSignInToGitLab({ code, state }: { code: string; state: string }) { let verifier = gitLabStatesCache.get(state); if (PLAYWRIGHT) { verifier = 'test-verifier'; } if (!verifier) { - throw new Error( - 'Invalid state parameter. It looks like the authorization flow was not initiated by the app.' - ); + throw new Error('Invalid state parameter. It looks like the authorization flow was not initiated by the app.'); } const { clientId, redirectUri } = await getGitLabConfig(); const url = new URL(`${getGitLabOauthApiURL()}/oauth/token`); @@ -2133,10 +2090,10 @@ async function completeSignInToGitLab({ method: 'POST', }); - const { - access_token, - refresh_token, - } = await gitLabResponse.json() as { access_token: string; refresh_token: string }; + const { access_token, refresh_token } = (await gitLabResponse.json()) as { + access_token: string; + refresh_token: string; + }; gitLabStatesCache.delete(state); const existingGitLabCredentials = await models.gitCredentials.getByProvider('gitlab'); @@ -2147,7 +2104,7 @@ async function completeSignInToGitLab({ }, }); - const user = await gitLabUserResponse.json() as { + const user = (await gitLabUserResponse.json()) as { id: number; username: string; name: string; @@ -2230,39 +2187,81 @@ export interface GitServiceAPI { } export const registerGitServiceAPI = () => { - ipcMainHandle('git.loadGitRepository', (_, options: Parameters[0]) => loadGitRepository(options)); + ipcMainHandle('git.loadGitRepository', (_, options: Parameters[0]) => + loadGitRepository(options), + ); ipcMainHandle('git.getGitBranches', (_, options: Parameters[0]) => getGitBranches(options)); ipcMainHandle('git.gitFetchAction', (_, options: Parameters[0]) => gitFetchAction(options)); ipcMainHandle('git.gitLogLoader', (_, options: Parameters[0]) => gitLogLoader(options)); - ipcMainHandle('git.gitChangesLoader', (_, options: Parameters[0]) => gitChangesLoader(options)); + ipcMainHandle('git.gitChangesLoader', (_, options: Parameters[0]) => + gitChangesLoader(options), + ); ipcMainHandle('git.canPushLoader', (_, options: Parameters[0]) => canPushLoader(options)); - ipcMainHandle('git.cloneGitRepo', (_, options: Parameters[0]) => cloneGitRepoAction(options)); - ipcMainHandle('git.initGitRepoClone', (_, options: Parameters[0]) => initGitRepoCloneAction(options)); - ipcMainHandle('git.updateGitRepo', (_, options: Parameters[0]) => updateGitRepoAction(options)); - ipcMainHandle('git.resetGitRepo', (_, options: Parameters[0]) => resetGitRepoAction(options)); - ipcMainHandle('git.commitToGitRepo', (_, options: Parameters[0]) => commitToGitRepoAction(options)); - ipcMainHandle('git.commitAndPushToGitRepo', (_, options: Parameters[0]) => commitAndPushToGitRepoAction(options)); - ipcMainHandle('git.createNewGitBranch', (_, options: Parameters[0]) => createNewGitBranchAction(options)); - ipcMainHandle('git.checkoutGitBranch', (_, options: Parameters[0]) => checkoutGitBranchAction(options)); + ipcMainHandle('git.cloneGitRepo', (_, options: Parameters[0]) => + cloneGitRepoAction(options), + ); + ipcMainHandle('git.initGitRepoClone', (_, options: Parameters[0]) => + initGitRepoCloneAction(options), + ); + ipcMainHandle('git.updateGitRepo', (_, options: Parameters[0]) => + updateGitRepoAction(options), + ); + ipcMainHandle('git.resetGitRepo', (_, options: Parameters[0]) => + resetGitRepoAction(options), + ); + ipcMainHandle('git.commitToGitRepo', (_, options: Parameters[0]) => + commitToGitRepoAction(options), + ); + ipcMainHandle('git.commitAndPushToGitRepo', (_, options: Parameters[0]) => + commitAndPushToGitRepoAction(options), + ); + ipcMainHandle('git.createNewGitBranch', (_, options: Parameters[0]) => + createNewGitBranchAction(options), + ); + ipcMainHandle('git.checkoutGitBranch', (_, options: Parameters[0]) => + checkoutGitBranchAction(options), + ); ipcMainHandle('git.mergeGitBranch', (_, options: Parameters[0]) => mergeGitBranch(options)); - ipcMainHandle('git.deleteGitBranch', (_, options: Parameters[0]) => deleteGitBranchAction(options)); - ipcMainHandle('git.pushToGitRemote', (_, options: Parameters[0]) => pushToGitRemoteAction(options)); - ipcMainHandle('git.pullFromGitRemote', (_, options: Parameters[0]) => pullFromGitRemote(options)); + ipcMainHandle('git.deleteGitBranch', (_, options: Parameters[0]) => + deleteGitBranchAction(options), + ); + ipcMainHandle('git.pushToGitRemote', (_, options: Parameters[0]) => + pushToGitRemoteAction(options), + ); + ipcMainHandle('git.pullFromGitRemote', (_, options: Parameters[0]) => + pullFromGitRemote(options), + ); ipcMainHandle('git.continueMerge', (_, options: Parameters[0]) => continueMerge(options)); - ipcMainHandle('git.discardChanges', (_, options: Parameters[0]) => discardChangesAction(options)); + ipcMainHandle('git.discardChanges', (_, options: Parameters[0]) => + discardChangesAction(options), + ); ipcMainHandle('git.gitStatus', (_, options: Parameters[0]) => gitStatusAction(options)); - ipcMainHandle('git.stageChanges', (_, options: Parameters[0]) => stageChangesAction(options)); - ipcMainHandle('git.unstageChanges', (_, options: Parameters[0]) => unstageChangesAction(options)); + ipcMainHandle('git.stageChanges', (_, options: Parameters[0]) => + stageChangesAction(options), + ); + ipcMainHandle('git.unstageChanges', (_, options: Parameters[0]) => + unstageChangesAction(options), + ); ipcMainHandle('git.diffFileLoader', (_, options: Parameters[0]) => diffFileLoader(options)); - ipcMainHandle('git.getRepositoryDirectoryTree', (_, options: Parameters[0]) => getRepositoryDirectoryTree(options)); + ipcMainHandle('git.getRepositoryDirectoryTree', (_, options: Parameters[0]) => + getRepositoryDirectoryTree(options), + ); ipcMainHandle('git.initSignInToGitHub', () => initSignInToGitHub()); - ipcMainHandle('git.completeSignInToGitHub', (_, options: Parameters[0]) => completeSignInToGitHub(options)); + ipcMainHandle('git.completeSignInToGitHub', (_, options: Parameters[0]) => + completeSignInToGitHub(options), + ); ipcMainHandle('git.signOutOfGitHub', () => signOutOfGitHub()); - ipcMainHandle('git.getGitHubRepositories', (_, options: Parameters[0]) => getGitHubRepositories(options)); - ipcMainHandle('git.getGitHubRepository', (_, options: Parameters[0]) => getGitHubRepository(options)); + ipcMainHandle('git.getGitHubRepositories', (_, options: Parameters[0]) => + getGitHubRepositories(options), + ); + ipcMainHandle('git.getGitHubRepository', (_, options: Parameters[0]) => + getGitHubRepository(options), + ); ipcMainHandle('git.initSignInToGitLab', () => initSignInToGitLab()); - ipcMainHandle('git.completeSignInToGitLab', (_, options: Parameters[0]) => completeSignInToGitLab(options)); + ipcMainHandle('git.completeSignInToGitLab', (_, options: Parameters[0]) => + completeSignInToGitLab(options), + ); ipcMainHandle('git.signOutOfGitLab', () => signOutOfGitLab()); }; diff --git a/packages/insomnia/src/main/install-plugin.ts b/packages/insomnia/src/main/install-plugin.ts index 7369eb0c33..d2f7b43ea5 100644 --- a/packages/insomnia/src/main/install-plugin.ts +++ b/packages/insomnia/src/main/install-plugin.ts @@ -50,7 +50,11 @@ export default async function (lookupName: string) { info = await _isInsomniaPlugin(lookupName); // Get actual module name without version suffixes and things const moduleName = info.name; - const pluginDir = path.join(process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData'), 'plugins', moduleName); + const pluginDir = path.join( + process.env['INSOMNIA_DATA_PATH'] || electron.app.getPath('userData'), + 'plugins', + moduleName, + ); // Make plugin directory await mkdir(pluginDir, { recursive: true }); @@ -275,7 +279,6 @@ function _getYarnPath() { return path.resolve(app.getAppPath(), './bin/yarn-standalone.js'); } return path.resolve(app.getAppPath(), '../bin/yarn-standalone.js'); - } function escape(p: string) { @@ -285,5 +288,4 @@ function escape(p: string) { } // Escape whitespace and parenthesis with backslashes for Unix paths return p.replace(/([\s()])/g, '\\$1'); - } diff --git a/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts b/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts index 8f070d80cf..1646068ab5 100644 --- a/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts +++ b/packages/insomnia/src/main/ipc/__tests__/grpc.test.ts @@ -1,9 +1,4 @@ -import type { - AnyMessage, - MethodInfo, - PartialMessage, - ServiceType, -} from '@bufbuild/protobuf'; +import type { AnyMessage, MethodInfo, PartialMessage, ServiceType } from '@bufbuild/protobuf'; import type { UnaryResponse } from '@connectrpc/connect'; import { createConnectTransport } from '@connectrpc/connect-node'; import * as grpcReflection from 'grpc-reflection-js'; @@ -16,7 +11,6 @@ vi.mock('grpc-reflection-js'); vi.mock('@connectrpc/connect-node'); describe('loadMethodsFromReflection', () => { - describe('one service reflection', () => { beforeEach(() => { // we want to test that the values that are passed to axios are returned in the config key @@ -48,13 +42,15 @@ describe('loadMethodsFromReflection', () => { metadata: [], reflectionApi: { enabled: false, apiKey: '', url: '', module: '' }, }); - expect(methods).toStrictEqual([{ - type: 'unary', - fullPath: '/FooService/Foo', - example: { - foo: 'Hello', + expect(methods).toStrictEqual([ + { + type: 'unary', + fullPath: '/FooService/Foo', + example: { + foo: 'Hello', + }, }, - }]); + ]); }); }); @@ -89,13 +85,15 @@ describe('loadMethodsFromReflection', () => { metadata: [], reflectionApi: { enabled: false, apiKey: '', url: '', module: '' }, }); - expect(methods).toStrictEqual([{ - type: 'unary', - fullPath: '/FooService/format', - example: { - foo: 'Hello', + expect(methods).toStrictEqual([ + { + type: 'unary', + fullPath: '/FooService/format', + example: { + foo: 'Hello', + }, }, - }]); + ]); }); }); @@ -142,53 +140,54 @@ describe('loadMethodsFromReflection', () => { metadata: [], reflectionApi: { enabled: false, apiKey: '', url: '', module: '' }, }); - expect(methods).toStrictEqual([{ - type: 'unary', - fullPath: '/FooService/Foo', - example: { - foo: 'Hello', + expect(methods).toStrictEqual([ + { + type: 'unary', + fullPath: '/FooService/Foo', + example: { + foo: 'Hello', + }, }, - }, { - type: 'unary', - fullPath: '/BarService/Bar', - example: { - bar: 'Hello', + { + type: 'unary', + fullPath: '/BarService/Bar', + example: { + bar: 'Hello', + }, }, - }]); + ]); }); }); describe('buf reflection api', () => { it('loads module', async () => { - (createConnectTransport as unknown as vi.Mock).mockImplementation( - options => { - expect(options.baseUrl).toStrictEqual('https://buf.build'); - return { - async unary( - service: ServiceType, - method: MethodInfo, - _: AbortSignal | undefined, - __: number | undefined, - header: HeadersInit | undefined, - input: PartialMessage - ): Promise { - expect(new Headers(header).get('Authorization')).toStrictEqual('Bearer TEST_KEY'); - expect(input).toStrictEqual({ module: 'buf.build/connectrpc/eliza' }); - return { - service: service, - method: method, - header: new Headers(), - trailer: new Headers(), - stream: false, - // Output of running `buf curl https://buf.build/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet --data '{"module": "buf.build/connectrpc/eliza"}' --schema buf.build/bufbuild/reflect -H 'Authorization: Bearer buf-token'` - message: method.O.fromJsonString( - '{"fileDescriptorSet":{"file":[{"name":"connectrpc/eliza/v1/eliza.proto","package":"connectrpc.eliza.v1","messageType":[{"name":"SayRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"SayResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"IntroduceRequest","field":[{"name":"name","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"name"}]},{"name":"IntroduceResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]}],"service":[{"name":"ElizaService","method":[{"name":"Say","inputType":".connectrpc.eliza.v1.SayRequest","outputType":".connectrpc.eliza.v1.SayResponse","options":{"idempotencyLevel":"NO_SIDE_EFFECTS"}},{"name":"Converse","inputType":".connectrpc.eliza.v1.ConverseRequest","outputType":".connectrpc.eliza.v1.ConverseResponse","options":{},"clientStreaming":true,"serverStreaming":true},{"name":"Introduce","inputType":".connectrpc.eliza.v1.IntroduceRequest","outputType":".connectrpc.eliza.v1.IntroduceResponse","options":{},"serverStreaming":true}]}],"syntax":"proto3"}]},"version":"233fca715f49425581ec0a1b660be886"}' - ), - }; - }, - }; - } - ); + (createConnectTransport as unknown as vi.Mock).mockImplementation(options => { + expect(options.baseUrl).toStrictEqual('https://buf.build'); + return { + async unary( + service: ServiceType, + method: MethodInfo, + _: AbortSignal | undefined, + __: number | undefined, + header: HeadersInit | undefined, + input: PartialMessage, + ): Promise { + expect(new Headers(header).get('Authorization')).toStrictEqual('Bearer TEST_KEY'); + expect(input).toStrictEqual({ module: 'buf.build/connectrpc/eliza' }); + return { + service: service, + method: method, + header: new Headers(), + trailer: new Headers(), + stream: false, + // Output of running `buf curl https://buf.build/buf.reflect.v1beta1.FileDescriptorSetService/GetFileDescriptorSet --data '{"module": "buf.build/connectrpc/eliza"}' --schema buf.build/bufbuild/reflect -H 'Authorization: Bearer buf-token'` + message: method.O.fromJsonString( + '{"fileDescriptorSet":{"file":[{"name":"connectrpc/eliza/v1/eliza.proto","package":"connectrpc.eliza.v1","messageType":[{"name":"SayRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"SayResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseRequest","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"ConverseResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]},{"name":"IntroduceRequest","field":[{"name":"name","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"name"}]},{"name":"IntroduceResponse","field":[{"name":"sentence","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_STRING","jsonName":"sentence"}]}],"service":[{"name":"ElizaService","method":[{"name":"Say","inputType":".connectrpc.eliza.v1.SayRequest","outputType":".connectrpc.eliza.v1.SayResponse","options":{"idempotencyLevel":"NO_SIDE_EFFECTS"}},{"name":"Converse","inputType":".connectrpc.eliza.v1.ConverseRequest","outputType":".connectrpc.eliza.v1.ConverseResponse","options":{},"clientStreaming":true,"serverStreaming":true},{"name":"Introduce","inputType":".connectrpc.eliza.v1.IntroduceRequest","outputType":".connectrpc.eliza.v1.IntroduceResponse","options":{},"serverStreaming":true}]}],"syntax":"proto3"}]},"version":"233fca715f49425581ec0a1b660be886"}', + ), + }; + }, + }; + }); const methods = await loadMethodsFromReflection({ url: 'foo.com', metadata: [], @@ -199,25 +198,23 @@ describe('loadMethodsFromReflection', () => { module: 'buf.build/connectrpc/eliza', }, }); - expect(methods).toStrictEqual( - [ - { - example: undefined, - fullPath: '/connectrpc.eliza.v1.ElizaService/Say', - type: 'unary', - }, - { - example: undefined, - fullPath: '/connectrpc.eliza.v1.ElizaService/Converse', - type: 'bidi', - }, - { - example: undefined, - fullPath: '/connectrpc.eliza.v1.ElizaService/Introduce', - type: 'server', - }, - ] - ); + expect(methods).toStrictEqual([ + { + example: undefined, + fullPath: '/connectrpc.eliza.v1.ElizaService/Say', + type: 'unary', + }, + { + example: undefined, + fullPath: '/connectrpc.eliza.v1.ElizaService/Converse', + type: 'bidi', + }, + { + example: undefined, + fullPath: '/connectrpc.eliza.v1.ElizaService/Introduce', + type: 'server', + }, + ]); }); }); }); diff --git a/packages/insomnia/src/main/ipc/automock.ts b/packages/insomnia/src/main/ipc/automock.ts index 31140d4234..fc17726f16 100644 --- a/packages/insomnia/src/main/ipc/automock.ts +++ b/packages/insomnia/src/main/ipc/automock.ts @@ -1,6 +1,6 @@ // From https://github.com/bloomrpc/bloomrpc-mock/blob/master/src/automock.ts // TODO simplify this and rename to generate example payload -import type { Field, Message, OneOf, Service} from 'protobufjs'; +import type { Field, Message, OneOf, Service } from 'protobufjs'; import { Enum, MapField, Type } from 'protobufjs'; import { v4 } from 'uuid'; @@ -13,51 +13,31 @@ export type ServiceMethodsPayload = Record MethodPayload>; const enum MethodType { request, - response + response, } /** * Mock method response */ -export function mockResponseMethods( - service: Service, - mocks?: void | {}, -) { - return mockMethodReturnType( - service, - MethodType.response, - mocks - ); +export function mockResponseMethods(service: Service, mocks?: void | {}) { + return mockMethodReturnType(service, MethodType.response, mocks); } /** * Mock methods request */ -export function mockRequestMethods( - service: Service, - mocks?: void | {}, -) { - return mockMethodReturnType( - service, - MethodType.request, - mocks - ); +export function mockRequestMethods(service: Service, mocks?: void | {}) { + return mockMethodReturnType(service, MethodType.request, mocks); } -function mockMethodReturnType( - service: Service, - type: MethodType, - mocks?: void | {}, -): ServiceMethodsPayload { +function mockMethodReturnType(service: Service, type: MethodType, mocks?: void | {}): ServiceMethodsPayload { const root = service.root; const serviceMethods = service.methods; return Object.keys(serviceMethods).reduce((methods: ServiceMethodsPayload, method: string) => { const serviceMethod = serviceMethods[method]; - const methodMessageType = type === MethodType.request - ? serviceMethod.requestType - : serviceMethod.responseType; + const methodMessageType = type === MethodType.request ? serviceMethod.requestType : serviceMethod.responseType; const messageType = root.lookupType(methodMessageType); @@ -136,8 +116,7 @@ function mockField(field: Field, stackDepth: StackDepth): any { return mockField(resolvedField, stackDepth); } - return mockPropertyValue; - + return mockPropertyValue; } function mockMapField(field: MapField, stackDepth: StackDepth): any { @@ -160,7 +139,6 @@ function mockMapField(field: MapField, stackDepth: StackDepth): any { } else if (resolvedType === null) { mockPropertyValue = {}; } - } return { @@ -173,9 +151,7 @@ function isProtoType(resolvedType: Enum | Type | null): resolvedType is Type { return false; } const fieldsArray: keyof Type = 'fieldsArray'; - return resolvedType instanceof Type || ( - fieldsArray in resolvedType && Array.isArray(resolvedType[fieldsArray]) - ); + return resolvedType instanceof Type || (fieldsArray in resolvedType && Array.isArray(resolvedType[fieldsArray])); } function pickOneOf(oneofs: OneOf[], stackDepth: StackDepth) { diff --git a/packages/insomnia/src/main/ipc/electron.ts b/packages/insomnia/src/main/ipc/electron.ts index dd5f00c7db..5958207952 100644 --- a/packages/insomnia/src/main/ipc/electron.ts +++ b/packages/insomnia/src/main/ipc/electron.ts @@ -1,4 +1,10 @@ -import type { IpcMainEvent, IpcMainInvokeEvent, MenuItemConstructorOptions, OpenDialogOptions, SaveDialogOptions } from 'electron'; +import type { + IpcMainEvent, + IpcMainInvokeEvent, + MenuItemConstructorOptions, + OpenDialogOptions, + SaveDialogOptions, +} from 'electron'; import { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, shell } from 'electron'; import { fnOrString } from '../../common/misc'; @@ -8,7 +14,7 @@ import { localTemplateTags } from '../../ui/components/templating/local-template import { invariant } from '../../utils/invariant'; export type HandleChannels = - 'authorizeUserInWindow' + | 'authorizeUserInWindow' | 'backup' | 'curl.event.findMany' | 'curl.open' @@ -72,13 +78,10 @@ export type HandleChannels = export const ipcMainHandle = ( channel: HandleChannels, - listener: ( - event: IpcMainInvokeEvent, - ...args: any[] - ) => Promise | any + listener: (event: IpcMainInvokeEvent, ...args: any[]) => Promise | any, ) => ipcMain.handle(channel, listener); export type MainOnChannels = - 'cancelCurlRequest' + | 'cancelCurlRequest' | 'clear' | 'curl.close' | 'curl.closeAll' @@ -113,7 +116,7 @@ export type MainOnChannels = | 'startExecution'; export type RendererOnChannels = - 'clear-all-models' + | 'clear-all-models' | 'clear-model' | 'nunjucks-context-menu-command' | 'contextMenuCommand' @@ -134,18 +137,12 @@ export type RendererOnChannels = export const ipcMainOn = ( channel: MainOnChannels, - listener: ( - event: IpcMainEvent, - ...args: any[] - ) => Promise | any + listener: (event: IpcMainEvent, ...args: any[]) => Promise | any, ) => ipcMain.on(channel, listener); export type OnceChannels = 'halfSecondAfterAppStart' | 'landingPageRendered'; export const ipcMainOnce = ( channel: OnceChannels, - listener: ( - event: IpcMainEvent, - ...args: any[] - ) => Promise | any + listener: (event: IpcMainEvent, ...args: any[]) => Promise | any, ) => ipcMain.once(channel, listener); const getTemplateValue = (arg: NunjucksParsedTagArg) => { @@ -158,97 +155,111 @@ const getTemplateValue = (arg: NunjucksParsedTagArg) => { return arg.defaultValue; }; export function registerElectronHandlers() { - ipcMainOn('show-nunjucks-context-menu', (event, options: { key: string; nunjucksTag: ReturnType }) => { - const { key, nunjucksTag } = options; - const sendNunjuckTagContextMsg = (type: NunjucksTagContextMenuAction) => { - event.sender.send('nunjucks-context-menu-command', { key, nunjucksTag: { ...nunjucksTag, type } }); - }; - try { - const baseTemplate: MenuItemConstructorOptions[] = nunjucksTag ? - [ - { - label: 'Edit', - click: () => sendNunjuckTagContextMsg('edit'), - }, - { - label: 'Copy', - click: () => { - clipboard.writeText(nunjucksTag.template); - }, - }, - { - label: 'Cut', - click: () => { - clipboard.writeText(nunjucksTag.template); - sendNunjuckTagContextMsg('delete'); - }, - }, - { - label: 'Delete', - click: () => sendNunjuckTagContextMsg('delete'), - }, - { type: 'separator' }, - ] : - [ - { - role: 'cut', - }, - { - role: 'copy', - }, - { - role: 'paste', - }, - { type: 'separator' }, - ]; - const localTemplate: MenuItemConstructorOptions[] = localTemplateTags - // sort alphabetically - .sort((a, b) => fnOrString(a.templateTag.displayName).localeCompare(fnOrString(b.templateTag.displayName))) - .map(l => { - const actions = l.templateTag.args?.[0]; - const needsEnterprisePlan = l.templateTag.needsEnterprisePlan || false; - const additionalArgs = l.templateTag.args?.slice(1); - const hasSubmenu = actions?.options?.length; - return { - label: fnOrString(l.templateTag.displayName), - ...(!hasSubmenu ? + ipcMainOn( + 'show-nunjucks-context-menu', + (event, options: { key: string; nunjucksTag: ReturnType }) => { + const { key, nunjucksTag } = options; + const sendNunjuckTagContextMsg = (type: NunjucksTagContextMenuAction) => { + event.sender.send('nunjucks-context-menu-command', { key, nunjucksTag: { ...nunjucksTag, type } }); + }; + try { + const baseTemplate: MenuItemConstructorOptions[] = nunjucksTag + ? [ { + label: 'Edit', + click: () => sendNunjuckTagContextMsg('edit'), + }, + { + label: 'Copy', click: () => { - const tag = `{% ${l.templateTag.name} ${l.templateTag.args?.map(getTemplateValue).join(', ')} %}`; - const displayName = l.templateTag.displayName; - event.sender.send('nunjucks-context-menu-command', { key, tag, needsEnterprisePlan, displayName }); + clipboard.writeText(nunjucksTag.template); }, - } : + }, { - submenu: actions?.options?.map(action => ({ - label: fnOrString(action.displayName), - click: () => { - const additionalTagFields = additionalArgs.length ? ', ' + additionalArgs.map(getTemplateValue).join(', ') : ''; - const displayName = action.displayName; - const tag = `{% ${l.templateTag.name} '${action.value}'${additionalTagFields} %}`; - event.sender.send('nunjucks-context-menu-command', { key, tag, needsEnterprisePlan, displayName }); - }, - })), - }), - }; - }); - const menu = Menu.buildFromTemplate([...baseTemplate, ...localTemplate]); - const win = BrowserWindow.fromWebContents(event.sender); - invariant(win, 'expected window'); - menu.popup({ window: win }); - } catch (e) { - console.error(e); - } - }); + label: 'Cut', + click: () => { + clipboard.writeText(nunjucksTag.template); + sendNunjuckTagContextMsg('delete'); + }, + }, + { + label: 'Delete', + click: () => sendNunjuckTagContextMsg('delete'), + }, + { type: 'separator' }, + ] + : [ + { + role: 'cut', + }, + { + role: 'copy', + }, + { + role: 'paste', + }, + { type: 'separator' }, + ]; + const localTemplate: MenuItemConstructorOptions[] = localTemplateTags + // sort alphabetically + .sort((a, b) => fnOrString(a.templateTag.displayName).localeCompare(fnOrString(b.templateTag.displayName))) + .map(l => { + const actions = l.templateTag.args?.[0]; + const needsEnterprisePlan = l.templateTag.needsEnterprisePlan || false; + const additionalArgs = l.templateTag.args?.slice(1); + const hasSubmenu = actions?.options?.length; + return { + label: fnOrString(l.templateTag.displayName), + ...(!hasSubmenu + ? { + click: () => { + const tag = `{% ${l.templateTag.name} ${l.templateTag.args?.map(getTemplateValue).join(', ')} %}`; + const displayName = l.templateTag.displayName; + event.sender.send('nunjucks-context-menu-command', { + key, + tag, + needsEnterprisePlan, + displayName, + }); + }, + } + : { + submenu: actions?.options?.map(action => ({ + label: fnOrString(action.displayName), + click: () => { + const additionalTagFields = additionalArgs.length + ? ', ' + additionalArgs.map(getTemplateValue).join(', ') + : ''; + const displayName = action.displayName; + const tag = `{% ${l.templateTag.name} '${action.value}'${additionalTagFields} %}`; + event.sender.send('nunjucks-context-menu-command', { + key, + tag, + needsEnterprisePlan, + displayName, + }); + }, + })), + }), + }; + }); + const menu = Menu.buildFromTemplate([...baseTemplate, ...localTemplate]); + const win = BrowserWindow.fromWebContents(event.sender); + invariant(win, 'expected window'); + menu.popup({ window: win }); + } catch (e) { + console.error(e); + } + }, + ); ipcMainOn('setMenuBarVisibility', (_, visible: boolean) => { - BrowserWindow.getAllWindows() - .forEach(window => { - // the `setMenuBarVisibility` signature uses `visible` semantics - window.setMenuBarVisibility(visible); - // the `setAutoHideMenu` signature uses `hide` semantics - const hide = !visible; - window.setAutoHideMenuBar(hide); - }); + BrowserWindow.getAllWindows().forEach(window => { + // the `setMenuBarVisibility` signature uses `visible` semantics + window.setMenuBarVisibility(visible); + // the `setAutoHideMenu` signature uses `hide` semantics + const hide = !visible; + window.setAutoHideMenuBar(hide); + }); }); ipcMainHandle('showOpenDialog', async (_, options: OpenDialogOptions) => { const { filePaths, canceled } = await dialog.showOpenDialog(options); @@ -276,7 +287,7 @@ export function registerElectronHandlers() { clipboard.clear(); }); - ipcMainOn('getPath', (event, name: Parameters[0]) => { + ipcMainOn('getPath', (event, name: Parameters<(typeof Electron.app)['getPath']>[0]) => { event.returnValue = app.getPath(name); }); @@ -284,16 +295,19 @@ export function registerElectronHandlers() { event.returnValue = app.getAppPath(); }); - ipcMainOn('showContextMenu', (event, options: { key: string; menuItems: MenuItemConstructorOptions[]; extra?: Record }) => { - const menuItems = options.menuItems.map(item => { - return { - ...item, - click: () => { - event.sender.send('contextMenuCommand', { key: options.key, label: item.label, extra: options.extra }); - }, - }; - }); - const menu = Menu.buildFromTemplate(menuItems); - menu.popup(); - }); + ipcMainOn( + 'showContextMenu', + (event, options: { key: string; menuItems: MenuItemConstructorOptions[]; extra?: Record }) => { + const menuItems = options.menuItems.map(item => { + return { + ...item, + click: () => { + event.sender.send('contextMenuCommand', { key: options.key, label: item.label, extra: options.extra }); + }, + }; + }); + const menu = Menu.buildFromTemplate(menuItems); + menu.popup(); + }, + ); } diff --git a/packages/insomnia/src/main/ipc/extractPostmanDataDump.ts b/packages/insomnia/src/main/ipc/extractPostmanDataDump.ts index d32eb3df7d..891da0cbf6 100644 --- a/packages/insomnia/src/main/ipc/extractPostmanDataDump.ts +++ b/packages/insomnia/src/main/ipc/extractPostmanDataDump.ts @@ -43,25 +43,27 @@ export default async function extractPostmanDataDumpHandler(_event: unknown, dat // get collections and environments listed in archive.json try { - files.filter(file => file !== archiveJsonFile).forEach(file => { - const id = path.basename(file.path, '.json'); - const oriFileName = path.basename(file.path); - if (id in archiveJsonData.collection) { - collectionList.push({ - contentStr: file.data.toString(), - oriFileName, - }); - } else if (id in archiveJsonData.environment) { - const fileContentStr = file.data.toString(); - const fileJson = JSON.parse(fileContentStr); - // Set the scope to environment, because it's not set in the file - fileJson._postman_variable_scope = 'environment'; - envList.push({ - contentStr: JSON.stringify(fileJson), - oriFileName, - }); - } - }); + files + .filter(file => file !== archiveJsonFile) + .forEach(file => { + const id = path.basename(file.path, '.json'); + const oriFileName = path.basename(file.path); + if (id in archiveJsonData.collection) { + collectionList.push({ + contentStr: file.data.toString(), + oriFileName, + }); + } else if (id in archiveJsonData.environment) { + const fileContentStr = file.data.toString(); + const fileJson = JSON.parse(fileContentStr); + // Set the scope to environment, because it's not set in the file + fileJson._postman_variable_scope = 'environment'; + envList.push({ + contentStr: JSON.stringify(fileJson), + oriFileName, + }); + } + }); } catch (err) { return { err: 'Failed to parse collection or environment files', diff --git a/packages/insomnia/src/main/ipc/grpc.ts b/packages/insomnia/src/main/ipc/grpc.ts index 1e730d7875..650cd6a2ef 100644 --- a/packages/insomnia/src/main/ipc/grpc.ts +++ b/packages/insomnia/src/main/ipc/grpc.ts @@ -106,24 +106,19 @@ interface MethodDefs { example?: Record; } -const getMethodsFromReflectionServer = async ( - reflectionApi: GrpcRequest['reflectionApi'] -): Promise => { +const getMethodsFromReflectionServer = async (reflectionApi: GrpcRequest['reflectionApi']): Promise => { const { url, module, apiKey } = reflectionApi; - const GetFileDescriptorSetRequest = proto3.makeMessageType( - 'buf.reflect.v1beta1.GetFileDescriptorSetRequest', - () => [ - { no: 1, name: 'module', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'version', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { - no: 3, - name: 'symbols', - kind: 'scalar', - T: 9 /* ScalarType.STRING */, - repeated: true, - }, - ] - ); + const GetFileDescriptorSetRequest = proto3.makeMessageType('buf.reflect.v1beta1.GetFileDescriptorSetRequest', () => [ + { no: 1, name: 'module', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'version', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 3, + name: 'symbols', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]); const GetFileDescriptorSetResponse = proto3.makeMessageType( 'buf.reflect.v1beta1.GetFileDescriptorSetResponse', () => [ @@ -134,7 +129,7 @@ const getMethodsFromReflectionServer = async ( T: ProtobufEsFileDescriptorSet, }, { no: 2, name: 'version', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - ] + ], ); const FileDescriptorSetService = { typeName: 'buf.reflect.v1beta1.FileDescriptorSetService', @@ -164,15 +159,13 @@ const getMethodsFromReflectionServer = async ( }, { headers, - } + }, ); const methodDefs: MethodDefs[] = []; if (res.fileDescriptorSet === undefined) { return []; } - const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer( - new Buffer(res.fileDescriptorSet.toBinary()) - ); + const packageDefinition = protoLoader.loadFileDescriptorSetFromBuffer(new Buffer(res.fileDescriptorSet.toBinary())); for (const definition of Object.values(packageDefinition)) { const serviceDefinition = asServiceDefinition(definition); if (serviceDefinition === null) { @@ -189,7 +182,7 @@ const getMethodsFromReflectionServer = async ( throw new Error('Invalid reflection server api key'); case Code.NotFound: throw new Error( - "The reflection server api key doesn't have access to the module or the module does not exists" + "The reflection server api key doesn't have access to the module or the module does not exists", ); default: throw error; @@ -214,7 +207,7 @@ const getMethodsFromReflection = async ( getChannelCredentials({ url: host, caCertificate, clientCert, clientKey, rejectUnauthorized }), grpcOptions, filterDisabledOrInvalidMetaData(metadata), - path + path, ); const services = await client.listServices(); const methodsPromises = services.map(async service => { @@ -222,25 +215,15 @@ const getMethodsFromReflection = async ( const fullService = fileContainingSymbol.lookupService(service); const mockedRequestMethods = mockRequestMethods(fullService); const descriptorMessage = fileContainingSymbol.toDescriptor('proto3'); - const packageDefinition = protoLoader.loadFileDescriptorSetFromObject( - descriptorMessage, - {} - ); + const packageDefinition = protoLoader.loadFileDescriptorSetFromObject(descriptorMessage, {}); const tryToGetMethods = () => { try { console.log('[grpc] loading service from reflection:', service); - const serviceDefinition = asServiceDefinition( - packageDefinition[service] - ); - invariant( - serviceDefinition, - `'${service}' was not a valid ServiceDefinition` - ); + const serviceDefinition = asServiceDefinition(packageDefinition[service]); + invariant(serviceDefinition, `'${service}' was not a valid ServiceDefinition`); const serviceMethods = Object.values(serviceDefinition); return serviceMethods.map(m => { - const methodName = Object.keys(mockedRequestMethods).find(name => - m.path.endsWith(`/${name}`) - ); + const methodName = Object.keys(mockedRequestMethods).find(name => m.path.endsWith(`/${name}`)); if (!methodName) { return m; } @@ -291,10 +274,7 @@ export interface GrpcMethodInfo { example?: Record; } -export const getMethodType = ({ - requestStream, - responseStream, -}: any): GrpcMethodType => { +export const getMethodType = ({ requestStream, responseStream }: any): GrpcMethodType => { if (requestStream && responseStream) { return 'bidi'; } @@ -313,10 +293,7 @@ export const getSelectedMethod = async ( ): Promise => { if (request.protoFileId) { const protoFile = await models.protoFile.getById(request.protoFileId); - invariant( - protoFile?.protoText, - `No proto file found for gRPC request ${request._id}` - ); + invariant(protoFile?.protoText, `No proto file found for gRPC request ${request._id}`); const { filePath, dirs } = await writeProtoFile(protoFile); const methods = await loadMethodsFromFilePath(filePath, dirs); invariant(methods, 'No methods found'); @@ -336,9 +313,7 @@ export const getSelectedMethod = async ( return methods.find(c => c.path === request.protoMethodName); }; export const getMethodsFromPackageDefinition = (packageDefinition: PackageDefinition): MethodDefs[] => { - return Object.values(packageDefinition) - .filter(isServiceDefinition) - .flatMap(Object.values); + return Object.values(packageDefinition).filter(isServiceDefinition).flatMap(Object.values); }; const isServiceDefinition = (definition: AnyDefinition): definition is ServiceDefinition => { @@ -357,13 +332,32 @@ const isEnumDefinition = (definition: AnyDefinition): definition is EnumTypeDefi return (definition as EnumTypeDefinition).format === 'Protocol Buffer 3 EnumDescriptorProto'; }; -const getChannelCredentials = ({ url, rejectUnauthorized, clientCert, clientKey, caCertificate }: { url: string; rejectUnauthorized: boolean; clientCert?: string; clientKey?: string; caCertificate?: string }): ChannelCredentials => { +const getChannelCredentials = ({ + url, + rejectUnauthorized, + clientCert, + clientKey, + caCertificate, +}: { + url: string; + rejectUnauthorized: boolean; + clientCert?: string; + clientKey?: string; + caCertificate?: string; +}): ChannelCredentials => { if (url.toLowerCase().startsWith('grpcs:')) { if (caCertificate && clientKey && clientCert) { - return ChannelCredentials.createSsl(Buffer.from(caCertificate, 'utf8'), Buffer.from(clientKey, 'utf8'), Buffer.from(clientCert, 'utf8'), { rejectUnauthorized }); + return ChannelCredentials.createSsl( + Buffer.from(caCertificate, 'utf8'), + Buffer.from(clientKey, 'utf8'), + Buffer.from(clientCert, 'utf8'), + { rejectUnauthorized }, + ); } if (clientKey && clientCert) { - return ChannelCredentials.createSsl(null, Buffer.from(clientKey, 'utf8'), Buffer.from(clientCert, 'utf8'), { rejectUnauthorized }); + return ChannelCredentials.createSsl(null, Buffer.from(clientKey, 'utf8'), Buffer.from(clientCert, 'utf8'), { + rejectUnauthorized, + }); } if (caCertificate) { return ChannelCredentials.createSsl(Buffer.from(caCertificate, 'utf8'), null, null, { rejectUnauthorized }); @@ -373,95 +367,102 @@ const getChannelCredentials = ({ url, rejectUnauthorized, clientCert, clientKey, return ChannelCredentials.createInsecure(); }; -export const start = ( - event: IpcMainEvent, - ipcParams: GrpcIpcRequestParams, -) => { +export const start = (event: IpcMainEvent, ipcParams: GrpcIpcRequestParams) => { const { request, rejectUnauthorized, clientCert, clientKey, caCertificate } = ipcParams; - getSelectedMethod(request, ipcParams)?.then(method => { - if (!method) { - event.reply('grpc.error', request._id, new Error(`The gRPC method ${request.protoMethodName} could not be found`)); - return; - } - const methodType = getMethodType(method); - // Create client - const { url, path } = parseGrpcUrl(request.url); - - if (!url) { - event.reply('grpc.error', request._id, new Error('URL not specified')); - return undefined; - } - // @ts-expect-error -- TSCONVERSION second argument should be provided, send an empty string? Needs testing - const Client = makeGenericClientConstructor({}); - const creds = getChannelCredentials({ url: request.url, rejectUnauthorized, clientCert, clientKey, caCertificate }); - const client = new Client(url, creds); - if (!client) { - return; - } - - try { - const messageBody = JSON.parse(request.body.text || ''); - const requestPath = path + method.path; - if (methodType === 'unary') { - const unaryCall = client.makeUnaryRequest( - requestPath, - method.requestSerialize, - method.responseDeserialize, - messageBody, - filterDisabledOrInvalidMetaData(request.metadata), - onUnaryResponse(event, request._id), + getSelectedMethod(request, ipcParams) + ?.then(method => { + if (!method) { + event.reply( + 'grpc.error', + request._id, + new Error(`The gRPC method ${request.protoMethodName} could not be found`), ); - unaryCall.on('status', (status: StatusObject) => event.reply('grpc.status', request._id, status)); - grpcCalls.set(request._id, unaryCall); - } else if (methodType === 'client') { - const clientCall = client.makeClientStreamRequest( - requestPath, - method.requestSerialize, - method.responseDeserialize, - filterDisabledOrInvalidMetaData(request.metadata), - onUnaryResponse(event, request._id)); - clientCall.on('status', (status: StatusObject) => event.reply('grpc.status', request._id, status)); - grpcCalls.set(request._id, clientCall); - } else if (methodType === 'server') { - const serverCall = client.makeServerStreamRequest( - requestPath, - method.requestSerialize, - method.responseDeserialize, - messageBody, - filterDisabledOrInvalidMetaData(request.metadata), - ); - onStreamingResponse(event, serverCall, request._id); - grpcCalls.set(request._id, serverCall); - } else if (methodType === 'bidi') { - const bidiCall = client.makeBidiStreamRequest( - requestPath, - method.requestSerialize, - method.responseDeserialize, - filterDisabledOrInvalidMetaData(request.metadata)); - onStreamingResponse(event, bidiCall, request._id); - grpcCalls.set(request._id, bidiCall); - } else { - throw new Error(`Unsupported method type: ${methodType}`); + return; } - // Update request stats - models.stats.incrementExecutedRequests(); - event.reply('grpc.start', request._id); + const methodType = getMethodType(method); + // Create client + const { url, path } = parseGrpcUrl(request.url); - } catch (error) { - // TODO: How do we want to handle this case, where the message cannot be parsed? - // Currently an error will be shown, but the stream will not be cancelled. + if (!url) { + event.reply('grpc.error', request._id, new Error('URL not specified')); + return undefined; + } + // @ts-expect-error -- TSCONVERSION second argument should be provided, send an empty string? Needs testing + const Client = makeGenericClientConstructor({}); + const creds = getChannelCredentials({ + url: request.url, + rejectUnauthorized, + clientCert, + clientKey, + caCertificate, + }); + const client = new Client(url, creds); + if (!client) { + return; + } + + try { + const messageBody = JSON.parse(request.body.text || ''); + const requestPath = path + method.path; + if (methodType === 'unary') { + const unaryCall = client.makeUnaryRequest( + requestPath, + method.requestSerialize, + method.responseDeserialize, + messageBody, + filterDisabledOrInvalidMetaData(request.metadata), + onUnaryResponse(event, request._id), + ); + unaryCall.on('status', (status: StatusObject) => event.reply('grpc.status', request._id, status)); + grpcCalls.set(request._id, unaryCall); + } else if (methodType === 'client') { + const clientCall = client.makeClientStreamRequest( + requestPath, + method.requestSerialize, + method.responseDeserialize, + filterDisabledOrInvalidMetaData(request.metadata), + onUnaryResponse(event, request._id), + ); + clientCall.on('status', (status: StatusObject) => event.reply('grpc.status', request._id, status)); + grpcCalls.set(request._id, clientCall); + } else if (methodType === 'server') { + const serverCall = client.makeServerStreamRequest( + requestPath, + method.requestSerialize, + method.responseDeserialize, + messageBody, + filterDisabledOrInvalidMetaData(request.metadata), + ); + onStreamingResponse(event, serverCall, request._id); + grpcCalls.set(request._id, serverCall); + } else if (methodType === 'bidi') { + const bidiCall = client.makeBidiStreamRequest( + requestPath, + method.requestSerialize, + method.responseDeserialize, + filterDisabledOrInvalidMetaData(request.metadata), + ); + onStreamingResponse(event, bidiCall, request._id); + grpcCalls.set(request._id, bidiCall); + } else { + throw new Error(`Unsupported method type: ${methodType}`); + } + // Update request stats + models.stats.incrementExecutedRequests(); + event.reply('grpc.start', request._id); + } catch (error) { + // TODO: How do we want to handle this case, where the message cannot be parsed? + // Currently an error will be shown, but the stream will not be cancelled. + event.reply('grpc.error', request._id, error); + } + return; + }) + .catch(error => { event.reply('grpc.error', request._id, error); - } - return; - }).catch(error => { - event.reply('grpc.error', request._id, error); - }); + }); }; -export const sendMessage = ( - event: IpcMainEvent, - { body, requestId }: GrpcIpcMessageParams, -) => { +export const sendMessage = (event: IpcMainEvent, { body, requestId }: GrpcIpcMessageParams) => { try { const messageBody = JSON.parse(body.text || ''); // HACK BUT DO NOT REMOVE @@ -484,7 +485,11 @@ export const sendMessage = ( export const commit = (requestId: string): void => grpcCalls.get(requestId)?.end(); export const cancel = (requestId: string): void => grpcCalls.get(requestId)?.cancel(); -const onStreamingResponse = (event: IpcMainEvent, call: ClientReadableStream | ClientDuplexStream, requestId: string) => { +const onStreamingResponse = ( + event: IpcMainEvent, + call: ClientReadableStream | ClientDuplexStream, + requestId: string, +) => { call.on('status', (status: StatusObject) => event.reply('grpc.status', requestId, status)); call.on('data', data => event.reply('grpc.data', requestId, data)); call.on('error', (error: ServiceError) => { @@ -510,23 +515,24 @@ const onStreamingResponse = (event: IpcMainEvent, call: ClientReadableStream (err: ServiceError | null, value?: Record) => { - if (!err) { - event.reply('grpc.data', requestId, value); - } - if (err && err.code !== status.CANCELLED) { - event.reply('grpc.error', requestId, err); - } - event.reply('grpc.end', requestId); - // @ts-expect-error -- TSCONVERSION channel not found in call - const channel = grpcCalls.get(requestId)?.call?.call.channel; - if (channel) { - channel.close(); - } else { - console.log(`[gRPC] failed to close channel for req=${requestId} because it was not found`); - } - grpcCalls.delete(requestId); -}; +const onUnaryResponse = + (event: IpcMainEvent, requestId: string) => (err: ServiceError | null, value?: Record) => { + if (!err) { + event.reply('grpc.data', requestId, value); + } + if (err && err.code !== status.CANCELLED) { + event.reply('grpc.error', requestId, err); + } + event.reply('grpc.end', requestId); + // @ts-expect-error -- TSCONVERSION channel not found in call + const channel = grpcCalls.get(requestId)?.call?.call.channel; + if (channel) { + channel.close(); + } else { + console.log(`[gRPC] failed to close channel for req=${requestId} because it was not found`); + } + grpcCalls.delete(requestId); + }; const filterDisabledOrInvalidMetaData = (metadata: GrpcRequestHeader[]): Metadata => { const grpcMetadata = new Metadata(); diff --git a/packages/insomnia/src/main/ipc/main.ts b/packages/insomnia/src/main/ipc/main.ts index f6b6244bec..a4deb2c012 100644 --- a/packages/insomnia/src/main/ipc/main.ts +++ b/packages/insomnia/src/main/ipc/main.ts @@ -5,11 +5,11 @@ import { app, BrowserWindow, type IpcRendererEvent, type MenuItemConstructorOpti import fs from 'fs'; import iconv from 'iconv-lite'; -import type { LandingPage} from '../../common/sentry'; +import type { LandingPage } from '../../common/sentry'; import { APP_START_TIME, SentryMetrics } from '../../common/sentry'; import type { HiddenBrowserWindowBridgeAPI } from '../../hidden-window'; import * as models from '../../models'; -import type { SegmentEvent} from '../analytics'; +import type { SegmentEvent } from '../analytics'; import { trackPageView, trackSegmentEvent } from '../analytics'; import { authorizeUserInWindow } from '../authorizeUserInWindow'; import { backup, restoreBackup } from '../backup'; @@ -17,7 +17,14 @@ import type { GitServiceAPI } from '../git-service'; import installPlugin from '../install-plugin'; import type { CurlBridgeAPI } from '../network/curl'; import { cancelCurlRequest, curlRequest } from '../network/libcurl-promise'; -import { addExecutionStep, completeExecutionStep, getExecution, startExecution, type TimingStep, updateLatestStepName } from '../network/request-timing'; +import { + addExecutionStep, + completeExecutionStep, + getExecution, + startExecution, + type TimingStep, + updateLatestStepName, +} from '../network/request-timing'; import type { WebSocketBridgeAPI } from '../network/websocket'; import { ipcMainHandle, ipcMainOn, ipcMainOnce, type RendererOnChannels } from './electron'; import extractPostmanDataDumpHandler from './extractPostmanDataDump'; @@ -48,7 +55,11 @@ export interface RendererToMainBridgeAPI { trackSegmentEvent: (options: { event: string; properties?: Record }) => void; trackPageView: (options: { name: string }) => void; showNunjucksContextMenu: (options: { key: string; nunjucksTag?: { template: string; range: MarkerRange } }) => void; - showContextMenu: (options: { key: string; menuItems: MenuItemConstructorOptions[]; extra?: Record }) => void; + showContextMenu: (options: { + key: string; + menuItems: MenuItemConstructorOptions[]; + extra?: Record; + }) => void; database: { caCertificate: { @@ -166,16 +177,19 @@ export function registerMainHandlers() { } }); - ipcMainOnce('landingPageRendered', (_, { landingPage, tags = {} }: { landingPage: LandingPage; tags?: Record }) => { - const duration = performance.now() - APP_START_TIME; - Sentry.metrics.distribution(SentryMetrics.APP_START_DURATION, duration, { - tags: { - landingPage, - ...tags, - }, - unit: 'millisecond', - }); - }); + ipcMainOnce( + 'landingPageRendered', + (_, { landingPage, tags = {} }: { landingPage: LandingPage; tags?: Record }) => { + const duration = performance.now() - APP_START_TIME; + Sentry.metrics.distribution(SentryMetrics.APP_START_DURATION, duration, { + tags: { + landingPage, + ...tags, + }, + unit: 'millisecond', + }); + }, + ); ipcMainHandle('extractJsonFileFromPostmanDataDumpArchive', extractPostmanDataDumpHandler); } diff --git a/packages/insomnia/src/main/network/curl.ts b/packages/insomnia/src/main/network/curl.ts index 9ca7e1b23e..a2f90d526d 100644 --- a/packages/insomnia/src/main/network/curl.ts +++ b/packages/insomnia/src/main/network/curl.ts @@ -61,11 +61,7 @@ export interface CurlCloseEvent { code: number; } -export type CurlEvent = - | CurlOpenEvent - | CurlMessageEvent - | CurlErrorEvent - | CurlCloseEvent; +export type CurlEvent = CurlOpenEvent | CurlMessageEvent | CurlErrorEvent | CurlCloseEvent; const CurlConnections = new Map(); const eventLogFileStreams = new Map(); @@ -77,9 +73,7 @@ const parseHeadersAndBuildTimeline = (url: string, headersWithStatus: HeaderInfo const statusCode = result?.code || 0; const httpVersion = result?.version; const responseHeaders = Object.entries(headers).map(([name, value]) => ({ name, value: value?.toString() || '' })); - const timeline = [ - { value: `Preparing request to ${url}`, name: 'Text', timestamp: Date.now() }, - ]; + const timeline = [{ value: `Preparing request to ${url}`, name: 'Text', timestamp: Date.now() }]; return { timeline, responseHeaders, statusCode, statusMessage, httpVersion }; }; interface OpenCurlRequestOptions { @@ -95,7 +89,7 @@ interface OpenCurlRequestOptions { } const openCurlConnection = async ( _event: Electron.IpcMainInvokeEvent, - options: OpenCurlRequestOptions + options: OpenCurlRequestOptions, ): Promise => { const existingConnection = CurlConnections.get(options.requestId); @@ -124,7 +118,7 @@ const openCurlConnection = async ( const caCert = await models.caCertificate.findByParentId(options.workspaceId); const caCertficatePath = caCert?.path || null; - const caCertificate = (caCertficatePath && (await fs.promises.readFile(caCertficatePath)).toString()); + const caCertificate = caCertficatePath && (await fs.promises.readFile(caCertficatePath)).toString(); try { if (!options.url) { @@ -171,7 +165,13 @@ const openCurlConnection = async ( if (errorCode) { const res = await models.response.getById(responseId); if (!res) { - createErrorResponse(responseId, request._id, responseEnvironmentId, timelinePath, error.message || 'Something went wrong'); + createErrorResponse( + responseId, + request._id, + responseEnvironmentId, + timelinePath, + error.message || 'Something went wrong', + ); } } }); @@ -204,88 +204,112 @@ const openCurlConnection = async ( return 0; }); - CurlConnections.get(options.requestId)?.on('stream', async (stream: Readable, _code: number, [headersWithStatus]: HeaderInfo[]) => { - for (const window of BrowserWindow.getAllWindows()) { - window.webContents.send(readyStateChannel, true); - } - const { timeline, responseHeaders, statusCode, statusMessage, httpVersion } = parseHeadersAndBuildTimeline(options.url, headersWithStatus); + CurlConnections.get(options.requestId)?.on( + 'stream', + async (stream: Readable, _code: number, [headersWithStatus]: HeaderInfo[]) => { + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send(readyStateChannel, true); + } + const { timeline, responseHeaders, statusCode, statusMessage, httpVersion } = parseHeadersAndBuildTimeline( + options.url, + headersWithStatus, + ); - const responsePatch: Partial = { - _id: responseId, - parentId: request._id, - environmentId: responseEnvironmentId, - headers: responseHeaders, - url: options.url, - statusCode, - statusMessage, - httpVersion, - elapsedTime: performance.now() - start, - timelinePath, - bodyPath: responseBodyPath, - settingSendCookies: request.settingSendCookies, - settingStoreCookies: request.settingStoreCookies, - bodyCompression: null, - }; - const settings = await models.settings.get(); - const res = await models.response.create(responsePatch, settings.maxHistoryResponses); - models.requestMeta.updateOrCreateByParentId(request._id, { activeResponseId: res._id }); + const responsePatch: Partial = { + _id: responseId, + parentId: request._id, + environmentId: responseEnvironmentId, + headers: responseHeaders, + url: options.url, + statusCode, + statusMessage, + httpVersion, + elapsedTime: performance.now() - start, + timelinePath, + bodyPath: responseBodyPath, + settingSendCookies: request.settingSendCookies, + settingStoreCookies: request.settingStoreCookies, + bodyCompression: null, + }; + const settings = await models.settings.get(); + const res = await models.response.create(responsePatch, settings.maxHistoryResponses); + models.requestMeta.updateOrCreateByParentId(request._id, { activeResponseId: res._id }); - if (request.settingStoreCookies) { - const setCookieStrings: string[] = getSetCookieHeaders(responseHeaders).map(h => h.value); - const totalSetCookies = setCookieStrings.length; - if (totalSetCookies) { - const currentUrl = request.url; - const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ setCookieStrings, currentUrl, cookieJar: options.cookieJar }); - rejectedCookies.forEach(errorMessage => timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() })); - const hasCookiesToPersist = totalSetCookies > rejectedCookies.length; - if (hasCookiesToPersist) { - await models.cookieJar.update(options.cookieJar, { cookies }); - timeline.push({ value: `Saved ${totalSetCookies} cookies`, name: 'Text', timestamp: Date.now() }); + if (request.settingStoreCookies) { + const setCookieStrings: string[] = getSetCookieHeaders(responseHeaders).map(h => h.value); + const totalSetCookies = setCookieStrings.length; + if (totalSetCookies) { + const currentUrl = request.url; + const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ + setCookieStrings, + currentUrl, + cookieJar: options.cookieJar, + }); + rejectedCookies.forEach(errorMessage => + timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() }), + ); + const hasCookiesToPersist = totalSetCookies > rejectedCookies.length; + if (hasCookiesToPersist) { + await models.cookieJar.update(options.cookieJar, { cookies }); + timeline.push({ value: `Saved ${totalSetCookies} cookies`, name: 'Text', timestamp: Date.now() }); + } } } - } - timeline.map(t => timelineFileStreams.get(options.requestId)?.write(JSON.stringify(t) + '\n')); + timeline.map(t => timelineFileStreams.get(options.requestId)?.write(JSON.stringify(t) + '\n')); - invariant(eventLogFileStreams.get(request._id), 'writableStream should be defined'); - for await (const chunk of stream) { - const messageEvent: CurlMessageEvent = { + invariant(eventLogFileStreams.get(request._id), 'writableStream should be defined'); + for await (const chunk of stream) { + const messageEvent: CurlMessageEvent = { + _id: uuidV4(), + requestId: options.requestId, + data: new TextDecoder('utf-8').decode(chunk), + type: 'message', + timestamp: Date.now(), + direction: 'INCOMING', + }; + eventLogFileStreams.get(options.requestId)?.write(JSON.stringify(messageEvent) + '\n'); + } + + // NOTE: when stream is closed by remote server + const closeEvent: CurlCloseEvent = { _id: uuidV4(), requestId: options.requestId, - data: new TextDecoder('utf-8').decode(chunk), - type: 'message', + type: 'close', timestamp: Date.now(), - direction: 'INCOMING', + statusCode, + reason: '', + code: 0, + wasClean: true, }; - eventLogFileStreams.get(options.requestId)?.write(JSON.stringify(messageEvent) + '\n'); - } - - // NOTE: when stream is closed by remote server - const closeEvent: CurlCloseEvent = { - _id: uuidV4(), - requestId: options.requestId, - type: 'close', - timestamp: Date.now(), - statusCode, - reason: '', - code: 0, - wasClean: true, - }; - CurlConnections.get(options.requestId)?.close(); - deleteRequestMaps(options.requestId, 'Closing connection', closeEvent); - for (const window of BrowserWindow.getAllWindows()) { - window.webContents.send(readyStateChannel, false); - } - }); + CurlConnections.get(options.requestId)?.close(); + deleteRequestMaps(options.requestId, 'Closing connection', closeEvent); + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send(readyStateChannel, false); + } + }, + ); curl.perform(); } catch (e) { console.error('unhandled error:', e); deleteRequestMaps(request._id, e.message || 'Something went wrong'); - createErrorResponse(responseId, request._id, responseEnvironmentId, timelinePath, e.message || 'Something went wrong'); + createErrorResponse( + responseId, + request._id, + responseEnvironmentId, + timelinePath, + e.message || 'Something went wrong', + ); } }; -const createErrorResponse = async (responseId: string, requestId: string, environmentId: string | null, timelinePath: string, message: string) => { +const createErrorResponse = async ( + responseId: string, + requestId: string, + environmentId: string | null, + timelinePath: string, + message: string, +) => { const settings = await models.settings.get(); const responsePatch = { _id: responseId, @@ -305,22 +329,19 @@ const deleteRequestMaps = async (requestId: string, message: string, event?: Cur } eventLogFileStreams.get(requestId)?.end(); eventLogFileStreams.delete(requestId); - timelineFileStreams.get(requestId)?.write(JSON.stringify({ value: message, name: 'Text', timestamp: Date.now() }) + '\n'); + timelineFileStreams + .get(requestId) + ?.write(JSON.stringify({ value: message, name: 'Text', timestamp: Date.now() }) + '\n'); timelineFileStreams.get(requestId)?.end(); timelineFileStreams.delete(requestId); CurlConnections.delete(requestId); }; -const getCurlReadyState = async ( - options: { requestId: string } -): Promise => { +const getCurlReadyState = async (options: { requestId: string }): Promise => { return CurlConnections.get(options.requestId)?.isOpen ?? false; }; -const closeCurlConnection = ( - _event: Electron.IpcMainInvokeEvent, - options: { requestId: string } -): void => { +const closeCurlConnection = (_event: Electron.IpcMainInvokeEvent, options: { requestId: string }): void => { if (!CurlConnections.get(options.requestId)) { return; } @@ -345,19 +366,22 @@ const closeCurlConnection = ( const closeAllCurlConnections = (): void => CurlConnections.forEach(curl => curl.isOpen && curl.close()); -const findMany = async ( - options: { responseId: string } -): Promise => { +const findMany = async (options: { responseId: string }): Promise => { const response = await models.response.getById(options.responseId); if (!response || !response.bodyPath) { return []; } const body = await fs.promises.readFile(response.bodyPath); - return body.toString().split('\n').filter(e => e?.trim()) - // Parse the message - .map(e => JSON.parse(e)) - // Reverse the list of messages so that we get the latest message first - .reverse() || []; + return ( + body + .toString() + .split('\n') + .filter(e => e?.trim()) + // Parse the message + .map(e => JSON.parse(e)) + // Reverse the list of messages so that we get the latest message first + .reverse() || [] + ); }; export interface CurlBridgeAPI { diff --git a/packages/insomnia/src/main/network/libcurl-promise.ts b/packages/insomnia/src/main/network/libcurl-promise.ts index c8bb2ef16f..0c075876d4 100644 --- a/packages/insomnia/src/main/network/libcurl-promise.ts +++ b/packages/insomnia/src/main/network/libcurl-promise.ts @@ -3,7 +3,16 @@ import { invariant } from '../../utils/invariant'; invariant(process.type !== 'renderer', 'Native abstractions for Nodejs module unavailable in renderer'); -import { Curl, CurlAuth, CurlCode, CurlFeature, CurlHttpVersion, CurlInfoDebug, CurlNetrc, CurlSslOpt } from '@getinsomnia/node-libcurl'; +import { + Curl, + CurlAuth, + CurlCode, + CurlFeature, + CurlHttpVersion, + CurlInfoDebug, + CurlNetrc, + CurlSslOpt, +} from '@getinsomnia/node-libcurl'; import { isValid } from 'date-fns'; import electron from 'electron'; import fs from 'fs'; @@ -13,7 +22,14 @@ import { parse as urlParse } from 'url'; import { v4 as uuidv4 } from 'uuid'; import { version } from '../../../package.json'; -import { AUTH_AWS_IAM, AUTH_DIGEST, AUTH_NETRC, AUTH_NTLM, CONTENT_TYPE_FORM_DATA, CONTENT_TYPE_FORM_URLENCODED } from '../../common/constants'; +import { + AUTH_AWS_IAM, + AUTH_DIGEST, + AUTH_NETRC, + AUTH_NTLM, + CONTENT_TYPE_FORM_DATA, + CONTENT_TYPE_FORM_URLENCODED, +} from '../../common/constants'; import { describeByteSize, hasAuthHeader } from '../../common/misc'; import type { ClientCertificate } from '../../models/client-certificate'; import type { RequestHeader } from '../../models/request'; @@ -97,170 +113,183 @@ const getDataDirectory = () => process.env.INSOMNIA_DATA_PATH || electron.app.ge // NOTE: this is a dictionary of functions to close open listeners const cancelCurlRequestHandlers: Record void> = {}; export const cancelCurlRequest = (id: string) => cancelCurlRequestHandlers[id](); -export const curlRequest = (options: CurlRequestOptions) => new Promise(async resolve => { - try { - const responsesDir = path.join(getDataDirectory(), 'responses'); - // TODO: remove this check, its only used for network.test.ts - await fs.promises.mkdir(responsesDir, { recursive: true }); - const responseBodyPath = path.join(responsesDir, uuidv4() + '.response'); +export const curlRequest = (options: CurlRequestOptions) => + new Promise(async resolve => { + try { + const responsesDir = path.join(getDataDirectory(), 'responses'); + // TODO: remove this check, its only used for network.test.ts + await fs.promises.mkdir(responsesDir, { recursive: true }); + const responseBodyPath = path.join(responsesDir, uuidv4() + '.response'); - const { requestId, req, finalUrl, settings, certificates, caCertficatePath, socketPath, authHeader, noDecompress = false } = options; - const caCert = (caCertficatePath && (await fs.promises.readFile(caCertficatePath)).toString()); + const { + requestId, + req, + finalUrl, + settings, + certificates, + caCertficatePath, + socketPath, + authHeader, + noDecompress = false, + } = options; + const caCert = caCertficatePath && (await fs.promises.readFile(caCertficatePath)).toString(); - const { curl, debugTimeline } = createConfiguredCurlInstance({ - req, - finalUrl, - settings, - caCert, - certificates, - socketPath, - noDecompress, - }); - const { method, body } = req; - // Only set CURLOPT_CUSTOMREQUEST if not HEAD or GET. - // See https://curl.haxx.se/libcurl/c/CURLOPT_CUSTOMREQUEST.html - // This is how you tell Curl to send a HEAD request - if (method.toUpperCase() === 'HEAD') { - curl.setOpt(Curl.option.NOBODY, 1); - } else if (method.toUpperCase() === 'POST') { // This is how you tell Curl to send a POST request - curl.setOpt(Curl.option.POST, 1); - } else { // IMPORTANT: Only use CUSTOMREQUEST for all but HEAD and POST - curl.setOpt(Curl.option.CUSTOMREQUEST, method); - } - - const requestBodyPath = await parseRequestBodyPath(body); - const requestBody = parseRequestBody({ body, method }); - const isMultipart = body.mimeType === CONTENT_TYPE_FORM_DATA && requestBodyPath; - let requestFileDescriptor: number | undefined; - const { authentication } = req; - if (requestBodyPath) { - // AWS IAM file upload not supported - invariant(authentication.type !== AUTH_AWS_IAM, 'AWS authentication not supported for provided body type'); - const { size: contentLength } = fs.statSync(requestBodyPath); - curl.setOpt(Curl.option.INFILESIZE_LARGE, contentLength); - curl.setOpt(Curl.option.UPLOAD, 1); - // We need this, otherwise curl will send it as a POST - curl.setOpt(Curl.option.CUSTOMREQUEST, method); - // read file into request and close file descriptor - requestFileDescriptor = fs.openSync(requestBodyPath, 'r'); - curl.setOpt(Curl.option.READDATA, requestFileDescriptor); - curl.on('end', () => closeReadFunction(isMultipart, requestFileDescriptor, requestBodyPath)); - curl.on('error', () => closeReadFunction(isMultipart, requestFileDescriptor, requestBodyPath)); - } else if (requestBody !== undefined) { - curl.setOpt(Curl.option.POSTFIELDS, requestBody); - } - - // NOTE: temporary workaround for testing mockbin api - if (process.env.PLAYWRIGHT) { - req.headers = [...req.headers, { name: 'X-Mockbin-Test', value: 'true' }]; - } - - const headerStrings = parseHeaderStrings({ req, requestBody, requestBodyPath, finalUrl, authHeader }); - curl.setOpt(Curl.option.HTTPHEADER, headerStrings); - - // Create instance and handlers, poke value options in, set up write and debug callbacks, listen for events - const responseBodyWriteStream = fs.createWriteStream(responseBodyPath); - // cancel request by id map - cancelCurlRequestHandlers[requestId] = () => { - if (requestFileDescriptor && responseBodyPath) { - closeReadFunction(isMultipart, requestFileDescriptor, requestBodyPath); + const { curl, debugTimeline } = createConfiguredCurlInstance({ + req, + finalUrl, + settings, + caCert, + certificates, + socketPath, + noDecompress, + }); + const { method, body } = req; + // Only set CURLOPT_CUSTOMREQUEST if not HEAD or GET. + // See https://curl.haxx.se/libcurl/c/CURLOPT_CUSTOMREQUEST.html + // This is how you tell Curl to send a HEAD request + if (method.toUpperCase() === 'HEAD') { + curl.setOpt(Curl.option.NOBODY, 1); + } else if (method.toUpperCase() === 'POST') { + // This is how you tell Curl to send a POST request + curl.setOpt(Curl.option.POST, 1); + } else { + // IMPORTANT: Only use CUSTOMREQUEST for all but HEAD and POST + curl.setOpt(Curl.option.CUSTOMREQUEST, method); } - curl.isOpen && curl.close(); - }; - // set up response writer - let responseBodyBytes = 0; - curl.setOpt(Curl.option.WRITEFUNCTION, buffer => { - responseBodyBytes += buffer.length; - responseBodyWriteStream.write(buffer); - return buffer.length; - }); + const requestBodyPath = await parseRequestBodyPath(body); + const requestBody = parseRequestBody({ body, method }); + const isMultipart = body.mimeType === CONTENT_TYPE_FORM_DATA && requestBodyPath; + let requestFileDescriptor: number | undefined; + const { authentication } = req; + if (requestBodyPath) { + // AWS IAM file upload not supported + invariant(authentication.type !== AUTH_AWS_IAM, 'AWS authentication not supported for provided body type'); + const { size: contentLength } = fs.statSync(requestBodyPath); + curl.setOpt(Curl.option.INFILESIZE_LARGE, contentLength); + curl.setOpt(Curl.option.UPLOAD, 1); + // We need this, otherwise curl will send it as a POST + curl.setOpt(Curl.option.CUSTOMREQUEST, method); + // read file into request and close file descriptor + requestFileDescriptor = fs.openSync(requestBodyPath, 'r'); + curl.setOpt(Curl.option.READDATA, requestFileDescriptor); + curl.on('end', () => closeReadFunction(isMultipart, requestFileDescriptor, requestBodyPath)); + curl.on('error', () => closeReadFunction(isMultipart, requestFileDescriptor, requestBodyPath)); + } else if (requestBody !== undefined) { + curl.setOpt(Curl.option.POSTFIELDS, requestBody); + } - curl.setOpt(Curl.option.DEBUGFUNCTION, (infoType, buffer) => { - const isSSLData = infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut; - const isEmpty = buffer.length === 0; - // Don't show cookie setting because this will display every domain in the jar - const isAddCookie = infoType === CurlInfoDebug.Text && buffer.toString('utf8').indexOf('Added cookie') === 0; - if (isSSLData || isEmpty || isAddCookie) { + // NOTE: temporary workaround for testing mockbin api + if (process.env.PLAYWRIGHT) { + req.headers = [...req.headers, { name: 'X-Mockbin-Test', value: 'true' }]; + } + + const headerStrings = parseHeaderStrings({ req, requestBody, requestBodyPath, finalUrl, authHeader }); + curl.setOpt(Curl.option.HTTPHEADER, headerStrings); + + // Create instance and handlers, poke value options in, set up write and debug callbacks, listen for events + const responseBodyWriteStream = fs.createWriteStream(responseBodyPath); + // cancel request by id map + cancelCurlRequestHandlers[requestId] = () => { + if (requestFileDescriptor && responseBodyPath) { + closeReadFunction(isMultipart, requestFileDescriptor, requestBodyPath); + } + curl.isOpen && curl.close(); + }; + + // set up response writer + let responseBodyBytes = 0; + curl.setOpt(Curl.option.WRITEFUNCTION, buffer => { + responseBodyBytes += buffer.length; + responseBodyWriteStream.write(buffer); + return buffer.length; + }); + + curl.setOpt(Curl.option.DEBUGFUNCTION, (infoType, buffer) => { + const isSSLData = infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut; + const isEmpty = buffer.length === 0; + // Don't show cookie setting because this will display every domain in the jar + const isAddCookie = infoType === CurlInfoDebug.Text && buffer.toString('utf8').indexOf('Added cookie') === 0; + if (isSSLData || isEmpty || isAddCookie) { + return 0; + } + + // NOTE: resolves "Text" from CurlInfoDebug[CurlInfoDebug.Text] + let name = CurlInfoDebug[infoType] as keyof typeof CurlInfoDebug; + let timelineMessage; + const isRequestData = infoType === CurlInfoDebug.DataOut; + if (isRequestData) { + // Ignore large post data messages + const isLessThan10KB = buffer.length / 1024 < (settings.maxTimelineDataSizeKB || 1); + timelineMessage = isLessThan10KB ? buffer.toString('utf8') : `(${describeByteSize(buffer.length)} hidden)`; + } + const isResponseData = infoType === CurlInfoDebug.DataIn; + if (isResponseData) { + timelineMessage = `Received ${describeByteSize(buffer.length)} chunk`; + name = 'Text'; + } + const value = timelineMessage || buffer.toString('utf8'); + debugTimeline.push({ name, value, timestamp: Date.now() }); return 0; - } + }); + // returns "rawHeaders" string in a buffer, rather than HeaderInfo[] type which is an object with deduped keys + // this provides support for multiple set-cookies and duplicated headers + curl.enable(CurlFeature.Raw); + // NOTE: legacy write end callback + curl.on('end', () => responseBodyWriteStream.end()); + curl.on('end', async (_1: any, _2: any, rawHeaders: Buffer) => { + const patch = { + bytesContent: responseBodyBytes, + bytesRead: curl.getInfo(Curl.info.SIZE_DOWNLOAD) as number, + elapsedTime: (curl.getInfo(Curl.info.TOTAL_TIME) as number) * 1000, + url: curl.getInfo(Curl.info.EFFECTIVE_URL) as string, + }; + curl.isOpen && curl.close(); + await waitForStreamToFinish(responseBodyWriteStream); - // NOTE: resolves "Text" from CurlInfoDebug[CurlInfoDebug.Text] - let name = CurlInfoDebug[infoType] as keyof typeof CurlInfoDebug; - let timelineMessage; - const isRequestData = infoType === CurlInfoDebug.DataOut; - if (isRequestData) { - // Ignore large post data messages - const isLessThan10KB = buffer.length / 1024 < (settings.maxTimelineDataSizeKB || 1); - timelineMessage = isLessThan10KB ? buffer.toString('utf8') : `(${describeByteSize(buffer.length)} hidden)`; - } - const isResponseData = infoType === CurlInfoDebug.DataIn; - if (isResponseData) { - timelineMessage = `Received ${describeByteSize(buffer.length)} chunk`; - name = 'Text'; - } - const value = timelineMessage || buffer.toString('utf8'); - debugTimeline.push({ name, value, timestamp: Date.now() }); - return 0; - }); - // returns "rawHeaders" string in a buffer, rather than HeaderInfo[] type which is an object with deduped keys - // this provides support for multiple set-cookies and duplicated headers - curl.enable(CurlFeature.Raw); - // NOTE: legacy write end callback - curl.on('end', () => responseBodyWriteStream.end()); - curl.on('end', async (_1: any, _2: any, rawHeaders: Buffer) => { + const headerResults = _parseHeaders(rawHeaders); + resolve({ patch, debugTimeline, headerResults, responseBodyPath }); + }); + // NOTE: legacy write end callback + curl.on('error', () => responseBodyWriteStream.end()); + curl.on('error', async (err, code) => { + const elapsedTime = (curl.getInfo(Curl.info.TOTAL_TIME) as number) * 1000; + curl.isOpen && curl.close(); + await waitForStreamToFinish(responseBodyWriteStream); + + // If libcurl can't decompress the response, retry without decompression + if (code === CurlCode.CURLE_BAD_CONTENT_ENCODING && !noDecompress) { + resolve(curlRequest({ ...options, noDecompress: true })); + return; + } + + let error = err + ''; + let statusMessage = 'Error'; + + if (code === CurlCode.CURLE_ABORTED_BY_CALLBACK) { + error = 'Request aborted'; + statusMessage = 'Abort'; + } + const patch = { + statusMessage, + error: error || 'Something went wrong', + elapsedTime, + }; + + // NOTE: legacy, default headerResults + resolve({ patch, debugTimeline, headerResults: [{ version: '', code: 0, reason: '', headers: [] }] }); + }); + curl.perform(); + } catch (error) { + console.error(error); const patch = { - bytesContent: responseBodyBytes, - bytesRead: curl.getInfo(Curl.info.SIZE_DOWNLOAD) as number, - elapsedTime: curl.getInfo(Curl.info.TOTAL_TIME) as number * 1000, - url: curl.getInfo(Curl.info.EFFECTIVE_URL) as string, + statusMessage: 'Error', + error: error.message || 'Something went wrong', + elapsedTime: 0, }; - curl.isOpen && curl.close(); - await waitForStreamToFinish(responseBodyWriteStream); - - const headerResults = _parseHeaders(rawHeaders); - resolve({ patch, debugTimeline, headerResults, responseBodyPath }); - }); - // NOTE: legacy write end callback - curl.on('error', () => responseBodyWriteStream.end()); - curl.on('error', async (err, code) => { - const elapsedTime = curl.getInfo(Curl.info.TOTAL_TIME) as number * 1000; - curl.isOpen && curl.close(); - await waitForStreamToFinish(responseBodyWriteStream); - - // If libcurl can't decompress the response, retry without decompression - if (code === CurlCode.CURLE_BAD_CONTENT_ENCODING && !noDecompress) { - resolve(curlRequest({ ...options, noDecompress: true })); - return; - } - - let error = err + ''; - let statusMessage = 'Error'; - - if (code === CurlCode.CURLE_ABORTED_BY_CALLBACK) { - error = 'Request aborted'; - statusMessage = 'Abort'; - } - const patch = { - statusMessage, - error: error || 'Something went wrong', - elapsedTime, - }; - - // NOTE: legacy, default headerResults - resolve({ patch, debugTimeline, headerResults: [{ version: '', code: 0, reason: '', headers: [] }] }); - }); - curl.perform(); - } catch (error) { - console.error(error); - const patch = { - statusMessage: 'Error', - error: error.message || 'Something went wrong', - elapsedTime: 0, - }; - resolve({ patch, debugTimeline: [], headerResults: [{ version: '', code: 0, reason: '', headers: [] }] }); - } -}); + resolve({ patch, debugTimeline: [], headerResults: [{ version: '', code: 0, reason: '', headers: [] }] }); + } + }); export const createConfiguredCurlInstance = ({ req, @@ -358,13 +387,18 @@ export const createConfiguredCurlInstance = ({ curl.setOpt(Curl.option.SSL_VERIFYHOST, 0); curl.setOpt(Curl.option.SSL_VERIFYPEER, 0); } - debugTimeline.push({ value: `${validateSSL ? 'Enable' : 'Disable'} SSL validation`, name: 'Text', timestamp: Date.now() }); + debugTimeline.push({ + value: `${validateSSL ? 'Enable' : 'Disable'} SSL validation`, + name: 'Text', + timestamp: Date.now(), + }); - const followRedirects = { - 'off': false, - 'on': true, - 'global': settings.followRedirects, - }[req.settingFollowRedirects] ?? true; + const followRedirects = + { + off: false, + on: true, + global: settings.followRedirects, + }[req.settingFollowRedirects] ?? true; curl.setOpt(Curl.option.FOLLOWLOCATION, followRedirects); @@ -382,14 +416,20 @@ export const createConfiguredCurlInstance = ({ } // set-cookies from previous redirects if (cookieJar.cookies.length) { - debugTimeline.push({ value: `Enable cookie sending with jar of ${cookieJar.cookies.length} cookie${cookieJar.cookies.length !== 1 ? 's' : ''}`, name: 'Text', timestamp: Date.now() }); + debugTimeline.push({ + value: `Enable cookie sending with jar of ${cookieJar.cookies.length} cookie${cookieJar.cookies.length !== 1 ? 's' : ''}`, + name: 'Text', + timestamp: Date.now(), + }); for (const cookie of cookieJar.cookies) { const setCookie = [ cookie.httpOnly ? `#HttpOnly_${cookie.domain}` : cookie.domain, cookie.hostOnly ? 'FALSE' : 'TRUE', cookie.path, cookie.secure ? 'TRUE' : 'FALSE', - cookie.expires && isValid(new Date(cookie.expires)) ? Math.round(new Date(cookie.expires).getTime() / 1000) : 0, + cookie.expires && isValid(new Date(cookie.expires)) + ? Math.round(new Date(cookie.expires).getTime() / 1000) + : 0, cookie.key, cookie.value, ].join('\t'); @@ -430,7 +470,7 @@ const closeReadFunction = (isMultipart: boolean, fd?: number, path?: string) => // NOTE: multipart files are combined before sending, so this file is deleted after // alt implementation to send one part at a time https://github.com/JCMais/node-libcurl/blob/develop/examples/04-multi.js if (isMultipart && path) { - fs.unlink(path, () => { }); + fs.unlink(path, () => {}); } }; @@ -443,21 +483,24 @@ export interface HeaderResult { export function _parseHeaders(buffer: Buffer): HeaderResult[] { // split on two new lines const redirects = buffer.toString('utf8').split(/\r?\n\r?\n|\r\r/g); - return redirects.filter(r => !!r.trim()).map(redirect => { - // split on one new line - const [first, ...rest] = redirect.split(/\r?\n|\r/g); - const headers = rest.map(l => l.split(/:\s(.+)/)) - .filter(([n]) => !!n) - .map(([name, value = '']) => ({ name, value })); + return redirects + .filter(r => !!r.trim()) + .map(redirect => { + // split on one new line + const [first, ...rest] = redirect.split(/\r?\n|\r/g); + const headers = rest + .map(l => l.split(/:\s(.+)/)) + .filter(([n]) => !!n) + .map(([name, value = '']) => ({ name, value })); - const [version, code, ...other] = first.split(/ +/g); - return { - version, - code: parseInt(code, 10), - reason: other.join(' '), - headers, - }; - }); + const [version, code, ...other] = first.split(/ +/g); + return { + version, + code: parseInt(code, 10), + reason: other.join(' '), + headers, + }; + }); } // NOTE: legacy, suspicious, could be simplified @@ -502,7 +545,7 @@ const parseRequestBodyPath = async (body: any) => { if (!isMultipartForm) { return body.fileName; } - const { filePath } = await buildMultipart(body.params || [],); + const { filePath } = await buildMultipart(body.params || []); return filePath; }; diff --git a/packages/insomnia/src/main/network/multipart.ts b/packages/insomnia/src/main/network/multipart.ts index 11b03eabf7..75bc830d29 100644 --- a/packages/insomnia/src/main/network/multipart.ts +++ b/packages/insomnia/src/main/network/multipart.ts @@ -45,7 +45,7 @@ export async function buildMultipart(params: RequestBodyParameter[]) { end: false, }); // TODO: remove non-null assertion - + totalSize += size!; }); } diff --git a/packages/insomnia/src/main/network/parse-header-strings.ts b/packages/insomnia/src/main/network/parse-header-strings.ts index 06beda6de6..690a7af096 100644 --- a/packages/insomnia/src/main/network/parse-header-strings.ts +++ b/packages/insomnia/src/main/network/parse-header-strings.ts @@ -2,12 +2,7 @@ import aws4 from 'aws4'; import clone from 'clone'; import { parse as urlParse } from 'url'; -import { - AUTH_AWS_IAM, - AUTH_DIGEST, - AUTH_NTLM, - CONTENT_TYPE_FORM_DATA, -} from '../../common/constants'; +import { AUTH_AWS_IAM, AUTH_DIGEST, AUTH_NTLM, CONTENT_TYPE_FORM_DATA } from '../../common/constants'; import { getContentTypeHeader, getHostHeader, @@ -46,7 +41,8 @@ export const parseHeaderStrings = ({ req, finalUrl, requestBody, requestBodyPath const isDigest = authentication.type === AUTH_DIGEST; const isNTLM = authentication.type === AUTH_NTLM; const isAWSIAM = authentication.type === AUTH_AWS_IAM; - const hasNoAuthorisationAndNotDisabledAWSBasicOrDigest = !hasAuthHeader(headers) && !authentication.disabled && !isAWSIAM && !isDigest && !isNTLM; + const hasNoAuthorisationAndNotDisabledAWSBasicOrDigest = + !hasAuthHeader(headers) && !authentication.disabled && !isAWSIAM && !isDigest && !isNTLM; if (hasNoAuthorisationAndNotDisabledAWSBasicOrDigest && authHeader) { headers.push(authHeader); } @@ -86,11 +82,15 @@ export const parseHeaderStrings = ({ req, finalUrl, requestBody, requestBodyPath headers.push({ name: 'content-type', value: DISABLE_HEADER_VALUE }); } - return headers.filter((h: any) => h.name) + return headers + .filter((h: any) => h.name) .map(({ name, value }: any) => - value === '' ? `${name};` // Curl needs a semicolon suffix to send empty header values - : value === DISABLE_HEADER_VALUE ? `${name}:` // Tell Curl NOT to send the header if value is null - : `${name}: ${value}`); + value === '' + ? `${name};` // Curl needs a semicolon suffix to send empty header values + : value === DISABLE_HEADER_VALUE + ? `${name}:` // Tell Curl NOT to send the header if value is null + : `${name}: ${value}`, + ); }; interface AWSOptions { @@ -107,20 +107,30 @@ interface AWSOptions { contentTypeHeader?: string; body?: string; } -export function _getAwsAuthHeaders({ authentication, url, method, hostHeader, contentTypeHeader, body }: AWSOptions): { name: string; value: any }[] { +export function _getAwsAuthHeaders({ + authentication, + url, + method, + hostHeader, + contentTypeHeader, + body, +}: AWSOptions): { name: string; value: any }[] { const { path, host } = urlParse(url); const onlyContentTypeHeader = contentTypeHeader ? { 'content-type': contentTypeHeader } : {}; const { service, region, accessKeyId, secretAccessKey, sessionToken } = authentication; - const signature = aws4.sign({ - service, - region, - body, - method, - headers: onlyContentTypeHeader, - path: path || undefined, - // AWS uses host header for signing so prioritize that if the user set it manually - host: hostHeader || host || undefined, - }, { accessKeyId, secretAccessKey, sessionToken }); + const signature = aws4.sign( + { + service, + region, + body, + method, + headers: onlyContentTypeHeader, + path: path || undefined, + // AWS uses host header for signing so prioritize that if the user set it manually + host: hostHeader || host || undefined, + }, + { accessKeyId, secretAccessKey, sessionToken }, + ); if (!signature.headers) { return []; } diff --git a/packages/insomnia/src/main/network/request-timing.ts b/packages/insomnia/src/main/network/request-timing.ts index cde9f3864c..1cf16df486 100644 --- a/packages/insomnia/src/main/network/request-timing.ts +++ b/packages/insomnia/src/main/network/request-timing.ts @@ -1,54 +1,48 @@ import { BrowserWindow } from 'electron'; export interface TimingStep { - stepName: string; - startedAt: number; - duration?: number; + stepName: string; + startedAt: number; + duration?: number; } export const executions = new Map(); -export const getExecution = (requestId?: string) => requestId ? executions.get(requestId) : []; +export const getExecution = (requestId?: string) => (requestId ? executions.get(requestId) : []); export const startExecution = (requestId: string) => executions.set(requestId, []); -export function addExecutionStep( - requestId: string, - stepName: string, -) { - // append to new step to execution - const record: TimingStep = { - stepName, - startedAt: Date.now(), - }; - const execution = [...(executions.get(requestId) || []), record]; - executions.set(requestId, execution); - for (const window of BrowserWindow.getAllWindows()) { - window.webContents.send(`syncTimers.${requestId}`, { executions: executions.get(requestId) }); - } +export function addExecutionStep(requestId: string, stepName: string) { + // append to new step to execution + const record: TimingStep = { + stepName, + startedAt: Date.now(), + }; + const execution = [...(executions.get(requestId) || []), record]; + executions.set(requestId, execution); + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send(`syncTimers.${requestId}`, { executions: executions.get(requestId) }); + } } export function completeExecutionStep(requestId: string) { - const latest = executions.get(requestId)?.at(-1); - if (latest) { - latest.duration = (Date.now() - latest.startedAt); - } - for (const window of BrowserWindow.getAllWindows()) { - window.webContents.send(`syncTimers.${requestId}`, { executions: executions.get(requestId) }); - } + const latest = executions.get(requestId)?.at(-1); + if (latest) { + latest.duration = Date.now() - latest.startedAt; + } + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send(`syncTimers.${requestId}`, { executions: executions.get(requestId) }); + } } -export function updateLatestStepName( - executionId: string, - stepName: string, -) { - const steps = executions.get(executionId) || []; - if (steps.length > 0) { - const latestStep = steps[steps.length - 1]; - latestStep.stepName = stepName; - executions.set(executionId, steps); - } +export function updateLatestStepName(executionId: string, stepName: string) { + const steps = executions.get(executionId) || []; + if (steps.length > 0) { + const latestStep = steps[steps.length - 1]; + latestStep.stepName = stepName; + executions.set(executionId, steps); + } - for (const window of BrowserWindow.getAllWindows()) { - window.webContents.send(`syncTimers.${executionId}`, { executions: executions.get(executionId) }); - } + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send(`syncTimers.${executionId}`, { executions: executions.get(executionId) }); + } } diff --git a/packages/insomnia/src/main/network/websocket.ts b/packages/insomnia/src/main/network/websocket.ts index 96f41ad074..c7ab70e3bd 100644 --- a/packages/insomnia/src/main/network/websocket.ts +++ b/packages/insomnia/src/main/network/websocket.ts @@ -5,13 +5,7 @@ import type { IncomingMessage } from 'http'; import path from 'path'; import tls, { type KeyObject, type PxfObject } from 'tls'; import { v4 as uuidV4 } from 'uuid'; -import { - type CloseEvent, - type ErrorEvent, - type Event, - type MessageEvent, - WebSocket, -} from 'ws'; +import { type CloseEvent, type ErrorEvent, type Event, type MessageEvent, WebSocket } from 'ws'; import { AUTH_API_KEY, AUTH_BASIC, AUTH_BEARER } from '../../common/constants'; import { jarFromCookies } from '../../common/cookies'; @@ -68,11 +62,7 @@ export type WebSocketCloseEvent = Omit & { timestamp: number; }; -export type WebSocketEvent = - | WebSocketOpenEvent - | WebSocketMessageEvent - | WebSocketErrorEvent - | WebSocketCloseEvent; +export type WebSocketEvent = WebSocketOpenEvent | WebSocketMessageEvent | WebSocketErrorEvent | WebSocketCloseEvent; export type WebSocketEventLog = WebSocketEvent[]; @@ -84,7 +74,10 @@ const parseResponseAndBuildTimeline = (url: string, incomingMessage: IncomingMes const statusMessage = incomingMessage.statusMessage || ''; const statusCode = incomingMessage.statusCode || 0; const httpVersion = incomingMessage.httpVersion; - const responseHeaders = Object.entries(incomingMessage.headers).map(([name, value]) => ({ name, value: value?.toString() || '' })); + const responseHeaders = Object.entries(incomingMessage.headers).map(([name, value]) => ({ + name, + value: value?.toString() || '', + })); const headersIn = responseHeaders.map(({ name, value }) => `${name}: ${value}`).join('\n'); const timeline = [ { value: `Preparing request to ${url}`, name: 'Text', timestamp: Date.now() }, @@ -108,7 +101,7 @@ interface OpenWebSocketRequestOptions { } const openWebSocketConnection = async ( _event: Electron.IpcMainInvokeEvent, - options: OpenWebSocketRequestOptions + options: OpenWebSocketRequestOptions, ): Promise => { const existingConnection = WebSocketConnections.get(options.requestId); @@ -117,7 +110,9 @@ const openWebSocketConnection = async ( return; } - const request = options.isGraphqlSubscriptionRequest ? await models.request.getById(options.requestId) : await webSocketRequest.getById(options.requestId); + const request = options.isGraphqlSubscriptionRequest + ? await models.request.getById(options.requestId) + : await webSocketRequest.getById(options.requestId); const responseId = generateId('res'); if (!request) { return; @@ -133,15 +128,16 @@ const openWebSocketConnection = async ( const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(options.workspaceId); // fallback to base environment const activeEnvironmentId = workspaceMeta.activeEnvironmentId; - const activeEnvironment = activeEnvironmentId && await models.environment.getById(activeEnvironmentId); - const environment = activeEnvironment || await models.environment.getOrCreateForParentId(options.workspaceId); + const activeEnvironment = activeEnvironmentId && (await models.environment.getById(activeEnvironmentId)); + const environment = activeEnvironment || (await models.environment.getOrCreateForParentId(options.workspaceId)); invariant(environment, 'failed to find environment ' + activeEnvironmentId); const responseEnvironmentId = environment ? environment._id : null; const caCert = await models.caCertificate.findByParentId(options.workspaceId); const caCertficatePath = caCert?.path; // attempt to read CA Certificate PEM from disk, fallback to root certificates - const caCertificate = (caCertficatePath && (await fs.promises.readFile(caCertficatePath)).toString()) || tls.rootCertificates.join('\n'); + const caCertificate = + (caCertficatePath && (await fs.promises.readFile(caCertficatePath)).toString()) || tls.rootCertificates.join('\n'); try { if (!options.url) { @@ -149,8 +145,10 @@ const openWebSocketConnection = async ( } const readyStateChannel = `webSocket.${request._id}.readyState`; - const reduceArrayToLowerCaseKeyedDictionary = (acc: Record, { name, value }: BaseWebSocketRequest['headers'][0]) => - ({ ...acc, [name.toLowerCase() || '']: value || '' }); + const reduceArrayToLowerCaseKeyedDictionary = ( + acc: Record, + { name, value }: BaseWebSocketRequest['headers'][0], + ) => ({ ...acc, [name.toLowerCase() || '']: value || '' }); const headers = options.headers; let url = options.url; let authCookie = null; @@ -161,7 +159,7 @@ const openWebSocketConnection = async ( headers.push(getBasicAuthHeader(username, password, encoding)); } if (options.authentication.type === AUTH_API_KEY) { - const { key = '', value = '', addTo } = options.authentication; // Ensure key is not undefined + const { key = '', value = '', addTo } = options.authentication; // Ensure key is not undefined if (addTo === HEADER) { headers.push({ name: key, value: value }); } else if (addTo === COOKIE) { @@ -197,17 +195,30 @@ const openWebSocketConnection = async ( const { passphrase, cert, key, pfx } = clientCertificate; if (cert) { - timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ value: `Adding SSL PEM certificate: ${cert}`, name: 'Text', timestamp: Date.now() }) + '\n'); + timelineFileStreams + .get(options.requestId) + ?.write( + JSON.stringify({ value: `Adding SSL PEM certificate: ${cert}`, name: 'Text', timestamp: Date.now() }) + + '\n', + ); pemCertificates.push(fs.readFileSync(cert, 'utf-8')); } if (key) { - timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ value: `Adding SSL KEY certificate: ${key}`, name: 'Text', timestamp: Date.now() }) + '\n'); + timelineFileStreams + .get(options.requestId) + ?.write( + JSON.stringify({ value: `Adding SSL KEY certificate: ${key}`, name: 'Text', timestamp: Date.now() }) + '\n', + ); pemCertificateKeys.push({ pem: fs.readFileSync(key, 'utf-8'), passphrase: passphrase ?? undefined }); } if (pfx) { - timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ value: `Adding SSL P12 certificate: ${pfx}`, name: 'Text', timestamp: Date.now() }) + '\n'); + timelineFileStreams + .get(options.requestId) + ?.write( + JSON.stringify({ value: `Adding SSL P12 certificate: ${pfx}`, name: 'Text', timestamp: Date.now() }) + '\n', + ); pfxCertificates.push({ buf: fs.readFileSync(pfx, 'utf-8'), passphrase: passphrase ?? undefined }); } }); @@ -221,11 +232,12 @@ const openWebSocketConnection = async ( } } - const followRedirects = { - 'off': false, - 'on': true, - 'global': settings.followRedirects, - }[request.settingFollowRedirects] ?? true; + const followRedirects = + { + off: false, + on: true, + global: settings.followRedirects, + }[request.settingFollowRedirects] ?? true; const protocols = lowerCasedEnabledHeaders['sec-websocket-protocol']?.split(',').map(p => p.trim()); const ws = new WebSocket(url, protocols, { headers: lowerCasedEnabledHeaders, @@ -242,7 +254,11 @@ const openWebSocketConnection = async ( ws.on('upgrade', async incomingMessage => { // @ts-expect-error -- private property const internalRequestHeader = ws._req._header; - const { timeline, responseHeaders, statusCode, statusMessage, httpVersion } = parseResponseAndBuildTimeline(url, incomingMessage, internalRequestHeader); + const { timeline, responseHeaders, statusCode, statusMessage, httpVersion } = parseResponseAndBuildTimeline( + url, + incomingMessage, + internalRequestHeader, + ); const responsePatch: Partial = { _id: responseId, parentId: request._id, @@ -268,8 +284,14 @@ const openWebSocketConnection = async ( const totalSetCookies = setCookieStrings.length; if (totalSetCookies) { const currentUrl = request.url; - const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ setCookieStrings, currentUrl, cookieJar: options.cookieJar }); - rejectedCookies.forEach(errorMessage => timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() })); + const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ + setCookieStrings, + currentUrl, + cookieJar: options.cookieJar, + }); + rejectedCookies.forEach(errorMessage => + timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() }), + ); const hasCookiesToPersist = totalSetCookies > rejectedCookies.length; if (hasCookiesToPersist) { await models.cookieJar.update(options.cookieJar, { cookies }); @@ -282,11 +304,17 @@ const openWebSocketConnection = async ( }); ws.on('unexpected-response', async (clientRequest, incomingMessage) => { incomingMessage.on('data', chunk => { - timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ value: chunk.toString(), name: 'DataOut', timestamp: Date.now() }) + '\n'); + timelineFileStreams + .get(options.requestId) + ?.write(JSON.stringify({ value: chunk.toString(), name: 'DataOut', timestamp: Date.now() }) + '\n'); }); // @ts-expect-error -- private property const internalRequestHeader = clientRequest._header; - const { timeline, responseHeaders, statusCode, statusMessage, httpVersion } = parseResponseAndBuildTimeline(url, incomingMessage, internalRequestHeader); + const { timeline, responseHeaders, statusCode, statusMessage, httpVersion } = parseResponseAndBuildTimeline( + url, + incomingMessage, + internalRequestHeader, + ); timeline.map(t => timelineFileStreams.get(options.requestId)?.write(JSON.stringify(t) + '\n')); const responsePatch: Partial = { _id: responseId, @@ -318,7 +346,11 @@ const openWebSocketConnection = async ( }; eventLogFileStreams.get(options.requestId)?.write(JSON.stringify(openEvent) + '\n'); - timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ value: 'WebSocket connection established', name: 'Text', timestamp: Date.now() }) + '\n'); + timelineFileStreams + .get(options.requestId) + ?.write( + JSON.stringify({ value: 'WebSocket connection established', name: 'Text', timestamp: Date.now() }) + '\n', + ); for (const window of BrowserWindow.getAllWindows()) { window.webContents.send(readyStateChannel, ws.readyState === WebSocket.OPEN); } @@ -381,14 +413,26 @@ const openWebSocketConnection = async ( window.webContents.send(readyStateChannel, ws.readyState === WebSocket.OPEN); } if (error.code) { - createErrorResponse(responseId, request._id, responseEnvironmentId, timelinePath, message || 'Something went wrong'); + createErrorResponse( + responseId, + request._id, + responseEnvironmentId, + timelinePath, + message || 'Something went wrong', + ); } }); } catch (e) { console.error('unhandled error:', e); deleteRequestMaps(request._id, e.message || 'Something went wrong'); - createErrorResponse(responseId, request._id, responseEnvironmentId, timelinePath, e.message || 'Something went wrong'); + createErrorResponse( + responseId, + request._id, + responseEnvironmentId, + timelinePath, + e.message || 'Something went wrong', + ); } }; @@ -419,7 +463,13 @@ const handleGraphQLWsMessage = (data: MessageEvent['data'], request: Request) => } }; -const createErrorResponse = async (responseId: string, requestId: string, environmentId: string | null, timelinePath: string, message: string) => { +const createErrorResponse = async ( + responseId: string, + requestId: string, + environmentId: string | null, + timelinePath: string, + message: string, +) => { const settings = await models.settings.get(); const responsePatch = { _id: responseId, @@ -433,21 +483,25 @@ const createErrorResponse = async (responseId: string, requestId: string, enviro models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: res._id }); }; -const deleteRequestMaps = async (requestId: string, message: string, event?: WebSocketCloseEvent | WebSocketErrorEvent) => { +const deleteRequestMaps = async ( + requestId: string, + message: string, + event?: WebSocketCloseEvent | WebSocketErrorEvent, +) => { if (event) { eventLogFileStreams.get(requestId)?.write(JSON.stringify(event) + '\n'); } eventLogFileStreams.get(requestId)?.end(); eventLogFileStreams.delete(requestId); - timelineFileStreams.get(requestId)?.write(JSON.stringify({ value: message, name: 'Text', timestamp: Date.now() }) + '\n'); + timelineFileStreams + .get(requestId) + ?.write(JSON.stringify({ value: message, name: 'Text', timestamp: Date.now() }) + '\n'); timelineFileStreams.get(requestId)?.end(); timelineFileStreams.delete(requestId); WebSocketConnections.delete(requestId); }; -const getWebSocketReadyState = async ( - options: { requestId: string } -): Promise => { +const getWebSocketReadyState = async (options: { requestId: string }): Promise => { return WebSocketConnections.get(options.requestId)?.readyState === WebSocket.OPEN; }; @@ -479,9 +533,7 @@ const sendPayload = async (ws: WebSocket, options: { payload: string; requestId: } }; -const sendWebSocketEvent = async ( - options: { payload: string; requestId: string } -): Promise => { +const sendWebSocketEvent = async (options: { payload: string; requestId: string }): Promise => { const ws = WebSocketConnections.get(options.requestId); if (!ws) { @@ -492,9 +544,7 @@ const sendWebSocketEvent = async ( sendPayload(ws, options); }; -const closeWebSocketConnection = ( - options: { requestId: string } -): void => { +const closeWebSocketConnection = (options: { requestId: string }): void => { const ws = WebSocketConnections.get(options.requestId); if (!ws) { return; @@ -504,19 +554,22 @@ const closeWebSocketConnection = ( const closeAllWebSocketConnections = (): void => WebSocketConnections.forEach(ws => ws.close()); -const findMany = async ( - options: { responseId: string } -): Promise => { +const findMany = async (options: { responseId: string }): Promise => { const response = await models.webSocketResponse.getById(options.responseId); if (!response || !response.eventLogPath) { return []; } const body = await fs.promises.readFile(response.eventLogPath); - return body.toString().split('\n').filter(e => e?.trim()) - // Parse the message - .map(e => JSON.parse(e)) - // Reverse the list of messages so that we get the latest message first - .reverse() || []; + return ( + body + .toString() + .split('\n') + .filter(e => e?.trim()) + // Parse the message + .map(e => JSON.parse(e)) + // Reverse the list of messages so that we get the latest message first + .reverse() || [] + ); }; export interface WebSocketBridgeAPI { @@ -533,10 +586,16 @@ export interface WebSocketBridgeAPI { } export const registerWebSocketHandlers = () => { ipcMainHandle('webSocket.open', openWebSocketConnection); - ipcMainHandle('webSocket.event.send', (_, options: Parameters[0]) => sendWebSocketEvent(options)); - ipcMainOn('webSocket.close', (_, options: Parameters[0]) => closeWebSocketConnection(options)); + ipcMainHandle('webSocket.event.send', (_, options: Parameters[0]) => + sendWebSocketEvent(options), + ); + ipcMainOn('webSocket.close', (_, options: Parameters[0]) => + closeWebSocketConnection(options), + ); ipcMainOn('webSocket.closeAll', closeAllWebSocketConnections); - ipcMainHandle('webSocket.readyState', (_, options: Parameters[0]) => getWebSocketReadyState(options)); + ipcMainHandle('webSocket.readyState', (_, options: Parameters[0]) => + getWebSocketReadyState(options), + ); ipcMainHandle('webSocket.event.findMany', (_, options: Parameters[0]) => findMany(options)); }; diff --git a/packages/insomnia/src/main/proxy.ts b/packages/insomnia/src/main/proxy.ts index 1a5a6cc41d..ab0efc53f8 100644 --- a/packages/insomnia/src/main/proxy.ts +++ b/packages/insomnia/src/main/proxy.ts @@ -49,7 +49,11 @@ export async function watchProxySettings() { const [event, doc] = change; const isSettingsUpdate = isSettings(doc) && event === 'update'; if (isSettingsUpdate) { - const hasProxyChanged = old.proxyEnabled !== doc.proxyEnabled || old.httpProxy !== doc.httpProxy || old.httpsProxy !== doc.httpsProxy || old.noProxy !== doc.noProxy; + const hasProxyChanged = + old.proxyEnabled !== doc.proxyEnabled || + old.httpProxy !== doc.httpProxy || + old.httpsProxy !== doc.httpsProxy || + old.noProxy !== doc.noProxy; if (hasProxyChanged) { updateProxy(); old = doc; diff --git a/packages/insomnia/src/main/sentry.ts b/packages/insomnia/src/main/sentry.ts index 2c09ab4466..289f229ac9 100644 --- a/packages/insomnia/src/main/sentry.ts +++ b/packages/insomnia/src/main/sentry.ts @@ -13,14 +13,14 @@ let enabled = false; */ export function sentryWatchAnalyticsEnabled() { models.settings.get().then(async settings => { - enabled = settings.enableAnalytics || await session.isLoggedIn(); + enabled = settings.enableAnalytics || (await session.isLoggedIn()); }); db.onChange(async (changes: ChangeBufferEvent[]) => { for (const change of changes) { const [event, doc] = change; if (isSettings(doc) && event === 'update') { - enabled = doc.enableAnalytics || await session.isLoggedIn(); + enabled = doc.enableAnalytics || (await session.isLoggedIn()); } if (event === 'insert' || event === 'update') { diff --git a/packages/insomnia/src/main/templating-worker-database.ts b/packages/insomnia/src/main/templating-worker-database.ts index dd367cb3e9..58376a1529 100644 --- a/packages/insomnia/src/main/templating-worker-database.ts +++ b/packages/insomnia/src/main/templating-worker-database.ts @@ -6,75 +6,74 @@ import type { Workspace } from '../models/workspace'; import { fetchRequestData, sendCurlAndWriteTimeline, tryToInterpolateRequest } from '../network/network'; export const resolveDbByKey = async (request: Request) => { - const url = new URL(request.url); - let result; - const body = await request.json(); - if (url.host === 'request.getById'.toLowerCase()) { - result = await models.request.getById(body.id); - } - if (url.host === 'request.getAncestors'.toLowerCase()) { - result = await db.withAncestors(body.request, body.types); - } - if (url.host === 'workspace.getById'.toLowerCase()) { - result = await models.workspace.getById(body.id); - } - if (url.host === 'oAuth2Token.getByRequestId'.toLowerCase()) { - result = await models.oAuth2Token.getByParentId(body.parentId); - } - if (url.host === 'cookieJar.getOrCreateForWorkspace'.toLowerCase()) { - result = await models.cookieJar.getOrCreateForParentId(body.id); - } - if (url.host === 'response.getLatestForRequestId'.toLowerCase()) { - result = await models.response.getLatestForRequest(body.requestId, body.environmentId); - } - if (url.host === 'response.getBodyBuffer'.toLowerCase()) { - result = await models.response.getBodyBuffer(body.response, body.readFailureValue); - } - if (url.host === 'pluginData.hasItem'.toLowerCase()) { - const doc = await models.pluginData.getByKey(body.pluginName, body.key); - result = doc !== null; - } - if (url.host === 'pluginData.setItem'.toLowerCase()) { - result = models.pluginData.upsertByKey(body.pluginName, body.key, String(body.value)); - } - if (url.host === 'pluginData.getItem'.toLowerCase()) { - const doc = await models.pluginData.getByKey(body.pluginName, body.key); - result = doc ? doc.value : null; - } - if (url.host === 'pluginData.removeItem'.toLowerCase()) { - result = models.pluginData.removeByKey(body.pluginName, body.key); - } - if (url.host === 'pluginData.clear'.toLowerCase()) { - result = models.pluginData.removeAll(body.pluginName); - } - if (url.host === 'pluginData.all'.toLowerCase()) { - const docs = await models.pluginData.all(body.pluginName) || []; - result = docs.map(d => ({ - value: d.value, - key: d.key, - })); - } - if (url.host === 'network.sendRequest'.toLowerCase()) { - const { request, - environment, - settings, - clientCertificates, - caCert, - timelinePath, - responseId, - } = await fetchRequestData(body.request._id); + const url = new URL(request.url); + let result; + const body = await request.json(); + if (url.host === 'request.getById'.toLowerCase()) { + result = await models.request.getById(body.id); + } + if (url.host === 'request.getAncestors'.toLowerCase()) { + result = await db.withAncestors(body.request, body.types); + } + if (url.host === 'workspace.getById'.toLowerCase()) { + result = await models.workspace.getById(body.id); + } + if (url.host === 'oAuth2Token.getByRequestId'.toLowerCase()) { + result = await models.oAuth2Token.getByParentId(body.parentId); + } + if (url.host === 'cookieJar.getOrCreateForWorkspace'.toLowerCase()) { + result = await models.cookieJar.getOrCreateForParentId(body.id); + } + if (url.host === 'response.getLatestForRequestId'.toLowerCase()) { + result = await models.response.getLatestForRequest(body.requestId, body.environmentId); + } + if (url.host === 'response.getBodyBuffer'.toLowerCase()) { + result = await models.response.getBodyBuffer(body.response, body.readFailureValue); + } + if (url.host === 'pluginData.hasItem'.toLowerCase()) { + const doc = await models.pluginData.getByKey(body.pluginName, body.key); + result = doc !== null; + } + if (url.host === 'pluginData.setItem'.toLowerCase()) { + result = models.pluginData.upsertByKey(body.pluginName, body.key, String(body.value)); + } + if (url.host === 'pluginData.getItem'.toLowerCase()) { + const doc = await models.pluginData.getByKey(body.pluginName, body.key); + result = doc ? doc.value : null; + } + if (url.host === 'pluginData.removeItem'.toLowerCase()) { + result = models.pluginData.removeByKey(body.pluginName, body.key); + } + if (url.host === 'pluginData.clear'.toLowerCase()) { + result = models.pluginData.removeAll(body.pluginName); + } + if (url.host === 'pluginData.all'.toLowerCase()) { + const docs = (await models.pluginData.all(body.pluginName)) || []; + result = docs.map(d => ({ + value: d.value, + key: d.key, + })); + } + if (url.host === 'network.sendRequest'.toLowerCase()) { + const { request, environment, settings, clientCertificates, caCert, timelinePath, responseId } = + await fetchRequestData(body.request._id); - const renderResult = await tryToInterpolateRequest({ request, environment: environment._id, purpose: 'send', extraInfo: body.extraInfo }); - const response = await sendCurlAndWriteTimeline( - renderResult.request, - clientCertificates, - caCert, - settings, - timelinePath, - responseId - ); - result = await models.response.create({ ...response, bodyCompression: null }, settings.maxHistoryResponses); - } + const renderResult = await tryToInterpolateRequest({ + request, + environment: environment._id, + purpose: 'send', + extraInfo: body.extraInfo, + }); + const response = await sendCurlAndWriteTimeline( + renderResult.request, + clientCertificates, + caCert, + settings, + timelinePath, + responseId, + ); + result = await models.response.create({ ...response, bodyCompression: null }, settings.maxHistoryResponses); + } - return new Response(JSON.stringify(result)); + return new Response(JSON.stringify(result)); }; diff --git a/packages/insomnia/src/main/updates.ts b/packages/insomnia/src/main/updates.ts index 3c12d273fe..2eba4e8f46 100644 --- a/packages/insomnia/src/main/updates.ts +++ b/packages/insomnia/src/main/updates.ts @@ -1,26 +1,20 @@ import { autoUpdater, BrowserWindow, dialog } from 'electron'; -import { - CHECK_FOR_UPDATES_INTERVAL, - getAppId, - getAppVersion, - isDevelopment, - UpdateURL, -} from '../common/constants'; +import { CHECK_FOR_UPDATES_INTERVAL, getAppId, getAppVersion, isDevelopment, UpdateURL } from '../common/constants'; import { delay } from '../common/misc'; import * as models from '../models/index'; import { invariant } from '../utils/invariant'; import { ipcMainOn } from './ipc/electron'; -export type UpdateStatus = | - 'Update Error' | - 'Up to Date' | - 'Downloading...' | - 'Performing backup...' | - 'Updated (Restart Required)' | - 'Checking' | - 'Updates Not Supported' | - 'Check Now'; +export type UpdateStatus = + | 'Update Error' + | 'Up to Date' + | 'Downloading...' + | 'Performing backup...' + | 'Updated (Restart Required)' + | 'Checking' + | 'Updates Not Supported' + | 'Check Now'; const isUpdateSupported = () => { if (process.platform === 'linux') { @@ -75,17 +69,19 @@ export const init = async () => { _sendUpdateStatus('Performing backup...'); _sendUpdateStatus('Updated (Restart Required)'); - dialog.showMessageBox({ - type: 'info', - buttons: ['Restart', 'Later'], - title: 'Application Update', - message: process.platform === 'win32' ? releaseNotes : releaseName, - detail: 'A new version of Insomnia has been downloaded. Restart the application to apply the updates.', - }).then(returnValue => { - if (returnValue.response === 0) { - autoUpdater.quitAndInstall(); - } - }); + dialog + .showMessageBox({ + type: 'info', + buttons: ['Restart', 'Later'], + title: 'Application Update', + message: process.platform === 'win32' ? releaseNotes : releaseName, + detail: 'A new version of Insomnia has been downloaded. Restart the application to apply the updates.', + }) + .then(returnValue => { + if (returnValue.response === 0) { + autoUpdater.quitAndInstall(); + } + }); }); const settings = await models.settings.get(); diff --git a/packages/insomnia/src/main/window-utils.ts b/packages/insomnia/src/main/window-utils.ts index 5ba3adafc2..31f5ea7953 100644 --- a/packages/insomnia/src/main/window-utils.ts +++ b/packages/insomnia/src/main/window-utils.ts @@ -451,12 +451,13 @@ export function createWindow({ firstLaunch }: { firstLaunch?: boolean } = {}): E role: 'minimize', }, // @ts-expect-error -- TSCONVERSION missing in official electron types - ...(isMac() ? [ - { - label: `${MNEMONIC_SYM}Close`, - role: 'close', - }, - ] + ...(isMac() + ? [ + { + label: `${MNEMONIC_SYM}Close`, + role: 'close', + }, + ] : []), ], }; @@ -581,7 +582,7 @@ export function createWindow({ firstLaunch }: { firstLaunch?: boolean } = {}): E { label: `${MNEMONIC_SYM}About`, click: aboutMenuClickHandler, - } + }, ); } diff --git a/packages/insomnia/src/models/__schemas__/model-schemas.ts b/packages/insomnia/src/models/__schemas__/model-schemas.ts index 94a581b602..9ff0e16c10 100644 --- a/packages/insomnia/src/models/__schemas__/model-schemas.ts +++ b/packages/insomnia/src/models/__schemas__/model-schemas.ts @@ -13,7 +13,7 @@ const toSchema = (obj: T): Schema => { const cloned = clone(obj); const output: Partial> = {}; - // @ts-expect-error -- mapping unsoundness + // @ts-expect-error -- mapping unsoundness Object.keys(cloned).forEach(key => { // @ts-expect-error -- mapping unsoundness output[key] = () => cloned[key]; diff --git a/packages/insomnia/src/models/__tests__/request-meta.test.ts b/packages/insomnia/src/models/__tests__/request-meta.test.ts index 2941bd0d7c..07abc99fee 100644 --- a/packages/insomnia/src/models/__tests__/request-meta.test.ts +++ b/packages/insomnia/src/models/__tests__/request-meta.test.ts @@ -3,7 +3,6 @@ import { describe, expect, it } from 'vitest'; import * as models from '../index'; describe('create()', () => { - it('fails when missing parentId', async () => { expect(() => models.requestMeta.create({ diff --git a/packages/insomnia/src/models/__tests__/request.test.ts b/packages/insomnia/src/models/__tests__/request.test.ts index 9479be84e4..73e991dda2 100644 --- a/packages/insomnia/src/models/__tests__/request.test.ts +++ b/packages/insomnia/src/models/__tests__/request.test.ts @@ -5,7 +5,6 @@ import { newBodyGraphQL, updateMimeType } from '../../ui/components/dropdowns/co import * as models from '../index'; describe('init()', () => { - it('contains all required fields', async () => { Date.now = vi.fn().mockReturnValue(1478795580200); expect(models.request.init()).toEqual({ @@ -33,7 +32,6 @@ describe('init()', () => { }); describe('create()', () => { - it('creates a valid request', async () => { Date.now = vi.fn().mockReturnValue(1478795580200); const request = await models.request.create({ @@ -82,7 +80,6 @@ describe('create()', () => { }); describe('updateMimeType()', () => { - it('adds header when does not exist', async () => { const request = await models.request.create({ name: 'My Request', @@ -175,7 +172,6 @@ describe('updateMimeType()', () => { }); describe('migrate()', () => { - it('migrates basic case', () => { const original = { headers: [], @@ -297,7 +293,7 @@ describe('migrate()', () => { const contentToMimeMap = { 'application/json; charset=utf-8': 'application/json', 'text/plain': 'text/plain', - malformed: 'malformed', + 'malformed': 'malformed', }; for (const contentType of Object.keys(contentToMimeMap)) { @@ -411,8 +407,7 @@ describe('migrate()', () => { describe('newBodyGraphQL()', () => { it('strips \\\\n characters', () => { - const input = - '{"query": "query getCustomer() {\\\\n id\\\\n name\\\\n email\\\\n __typename\\\\n }\\\\n"}'; + const input = '{"query": "query getCustomer() {\\\\n id\\\\n name\\\\n email\\\\n __typename\\\\n }\\\\n"}'; const expectedTextOutput = '{"query": "query getCustomer() { id name email __typename }"}'; const actualOutput = newBodyGraphQL(input); expect(actualOutput).toEqual({ diff --git a/packages/insomnia/src/models/__tests__/response.test.ts b/packages/insomnia/src/models/__tests__/response.test.ts index 4f036219dc..73ac29ec69 100644 --- a/packages/insomnia/src/models/__tests__/response.test.ts +++ b/packages/insomnia/src/models/__tests__/response.test.ts @@ -7,7 +7,6 @@ import zlib from 'zlib'; import * as models from '../../models'; describe('migrate()', () => { - it('does it', async () => { const bodyPath = path.join(tmpdir(), 'foo.zip'); fs.writeFileSync(bodyPath, zlib.gzipSync('Hello World!')); diff --git a/packages/insomnia/src/models/__tests__/workspace.test.ts b/packages/insomnia/src/models/__tests__/workspace.test.ts index f400c938c0..6216fa85c0 100644 --- a/packages/insomnia/src/models/__tests__/workspace.test.ts +++ b/packages/insomnia/src/models/__tests__/workspace.test.ts @@ -3,7 +3,6 @@ import { describe, expect, it } from 'vitest'; import * as models from '../index'; import { WorkspaceScopeKeys } from '../workspace'; describe('migrate()', () => { - it('migrates client certificates properly', async () => { const workspace = await models.workspace.create({ name: 'My Workspace', diff --git a/packages/insomnia/src/models/api-spec.ts b/packages/insomnia/src/models/api-spec.ts index 4fddf0d43f..7b51c95e12 100644 --- a/packages/insomnia/src/models/api-spec.ts +++ b/packages/insomnia/src/models/api-spec.ts @@ -20,9 +20,7 @@ export interface BaseApiSpec { export type ApiSpec = BaseModel & BaseApiSpec; -export const isApiSpec = (model: Pick): model is ApiSpec => ( - model.type === type -); +export const isApiSpec = (model: Pick): model is ApiSpec => model.type === type; export function init(): BaseApiSpec { return { @@ -40,10 +38,7 @@ export function getByParentId(workspaceId: string) { return db.getWhere(type, { parentId: workspaceId }); } -export async function getOrCreateForParentId( - workspaceId: string, - patch: Partial = {}, -) { +export async function getOrCreateForParentId(workspaceId: string, patch: Partial = {}) { const spec = await db.getWhere(type, { parentId: workspaceId, }); @@ -55,10 +50,7 @@ export async function getOrCreateForParentId( return spec; } -export async function updateOrCreateForParentId( - workspaceId: string, - patch: Partial = {}, -) { +export async function updateOrCreateForParentId(workspaceId: string, patch: Partial = {}) { const spec = await getOrCreateForParentId(workspaceId); return db.docUpdate(spec, patch); } diff --git a/packages/insomnia/src/models/ca-certificate.ts b/packages/insomnia/src/models/ca-certificate.ts index 9a65cc42f4..d2fe44c331 100644 --- a/packages/insomnia/src/models/ca-certificate.ts +++ b/packages/insomnia/src/models/ca-certificate.ts @@ -30,9 +30,7 @@ export function init(): BaseCaCertificate { }; } -export const isCaCertificate = (model: Pick): model is CaCertificate => ( - model.type === type -); +export const isCaCertificate = (model: Pick): model is CaCertificate => model.type === type; export function migrate(doc: CaCertificate) { return doc; @@ -46,10 +44,7 @@ export function create(patch: Partial = {}) { return db.docCreate(type, patch); } -export function update( - cert: CaCertificate, - patch: Partial = {}, -) { +export function update(cert: CaCertificate, patch: Partial = {}) { return db.docUpdate(cert, patch); } diff --git a/packages/insomnia/src/models/client-certificate.ts b/packages/insomnia/src/models/client-certificate.ts index dc585d6c10..53e78206e7 100644 --- a/packages/insomnia/src/models/client-certificate.ts +++ b/packages/insomnia/src/models/client-certificate.ts @@ -38,9 +38,7 @@ export function init(): BaseClientCertificate { }; } -export const isClientCertificate = (model: Pick): model is ClientCertificate => ( - model.type === type -); +export const isClientCertificate = (model: Pick): model is ClientCertificate => model.type === type; export function migrate(doc: ClientCertificate) { return doc; @@ -54,10 +52,7 @@ export function create(patch: Partial = {}) { return db.docCreate(type, patch); } -export function update( - cert: ClientCertificate, - patch: Partial = {}, -) { +export function update(cert: ClientCertificate, patch: Partial = {}) { return db.docUpdate(cert, patch); } diff --git a/packages/insomnia/src/models/cookie-jar.ts b/packages/insomnia/src/models/cookie-jar.ts index c11ec6e243..6e2777d4c6 100644 --- a/packages/insomnia/src/models/cookie-jar.ts +++ b/packages/insomnia/src/models/cookie-jar.ts @@ -37,9 +37,7 @@ export interface BaseCookieJar { export type CookieJar = BaseModel & BaseCookieJar; -export const isCookieJar = (model: Pick): model is CookieJar => ( - model.type === type -); +export const isCookieJar = (model: Pick): model is CookieJar => model.type === type; export function init() { return { @@ -77,8 +75,7 @@ export async function getOrCreateForParentId(parentId: string) { _id: `${prefix}_${crypto.createHash('sha1').update(parentId).digest('hex')}`, }); } - return cookieJars[0]; - + return cookieJars[0]; } export async function all() { diff --git a/packages/insomnia/src/models/environment.ts b/packages/insomnia/src/models/environment.ts index 7ca8fa40da..10ee15b08a 100644 --- a/packages/insomnia/src/models/environment.ts +++ b/packages/insomnia/src/models/environment.ts @@ -21,10 +21,7 @@ export const vaultEnvironmentMaskValue = '••••••'; export const canDuplicate = true; export const canSync = true; // for those keys do not need to add in model init method -export const optionalKeys = [ - 'kvPairData', - 'environmentType', -]; +export const optionalKeys = ['kvPairData', 'environmentType']; export interface BaseEnvironment { name: string; @@ -40,7 +37,7 @@ export interface BaseEnvironment { export enum EnvironmentType { JSON = 'json', - KVPAIR = 'kv' + KVPAIR = 'kv', } export enum EnvironmentKvPairDataType { JSON = 'json', @@ -186,9 +183,7 @@ export const removeAllSecrets = async (orgnizationIds: string[]) => { }); }; -export const isEnvironment = (model: Pick): model is Environment => ( - model.type === type -); +export const isEnvironment = (model: Pick): model is Environment => model.type === type; export function init() { return { diff --git a/packages/insomnia/src/models/git-repository.ts b/packages/insomnia/src/models/git-repository.ts index e4a72c0cf4..c1d3a7e5ec 100644 --- a/packages/insomnia/src/models/git-repository.ts +++ b/packages/insomnia/src/models/git-repository.ts @@ -50,9 +50,7 @@ export interface BaseGitRepository { uriNeedsMigration: boolean; } -export const isGitRepository = (model: Pick): model is GitRepository => ( - model.type === type -); +export const isGitRepository = (model: Pick): model is GitRepository => model.type === type; export function migrate(doc: GitRepository) { return doc; diff --git a/packages/insomnia/src/models/grpc-request-meta.ts b/packages/insomnia/src/models/grpc-request-meta.ts index 085adcd0a2..29aef3310a 100644 --- a/packages/insomnia/src/models/grpc-request-meta.ts +++ b/packages/insomnia/src/models/grpc-request-meta.ts @@ -19,9 +19,7 @@ interface BaseGrpcRequestMeta { export type GrpcRequestMeta = BaseModel & BaseGrpcRequestMeta; -export const isGrpcRequestMeta = (model: Pick): model is GrpcRequestMeta => ( - model.type === type -); +export const isGrpcRequestMeta = (model: Pick): model is GrpcRequestMeta => model.type === type; export function init() { return { @@ -69,14 +67,13 @@ export async function updateOrCreateByParentId(parentId: string, patch: Partial< if (requestMeta) { return update(requestMeta, patch); } - const newPatch = Object.assign( - { - parentId, - }, - patch, - ); - return create(newPatch); - + const newPatch = Object.assign( + { + parentId, + }, + patch, + ); + return create(newPatch); } export function all() { diff --git a/packages/insomnia/src/models/grpc-request.ts b/packages/insomnia/src/models/grpc-request.ts index a0eed52758..df3f645153 100644 --- a/packages/insomnia/src/models/grpc-request.ts +++ b/packages/insomnia/src/models/grpc-request.ts @@ -38,13 +38,9 @@ interface BaseGrpcRequest { export type GrpcRequest = BaseModel & BaseGrpcRequest; -export const isGrpcRequest = (model: Pick): model is GrpcRequest => ( - model.type === type -); +export const isGrpcRequest = (model: Pick): model is GrpcRequest => model.type === type; -export const isGrpcRequestId = (id?: string | null) => ( - id?.startsWith(`${prefix}_`) -); +export const isGrpcRequestId = (id?: string | null) => id?.startsWith(`${prefix}_`); export function init(): BaseGrpcRequest { return { diff --git a/packages/insomnia/src/models/helpers/__tests__/project.test.ts b/packages/insomnia/src/models/helpers/__tests__/project.test.ts index 14c446b0c4..943b710668 100644 --- a/packages/insomnia/src/models/helpers/__tests__/project.test.ts +++ b/packages/insomnia/src/models/helpers/__tests__/project.test.ts @@ -10,20 +10,10 @@ const remote0 = { name: '0', remoteId: 'notNull', _id: 'remote0' }; describe('sortProjects', () => { it('sorts projects by default > local > remote > name', () => { - const unSortedProjects = [ - remoteA, - defaultOrgProject, - remoteB, - remote0, - ]; + const unSortedProjects = [remoteA, defaultOrgProject, remoteB, remote0]; const result = sortProjects(unSortedProjects); - const sortedProjects = [ - defaultOrgProject, - remote0, - remoteA, - remoteB, - ]; + const sortedProjects = [defaultOrgProject, remote0, remoteA, remoteB]; expect(result).toEqual(sortedProjects); }); }); diff --git a/packages/insomnia/src/models/helpers/project.ts b/packages/insomnia/src/models/helpers/project.ts index 69e8313587..53031b9f71 100644 --- a/packages/insomnia/src/models/helpers/project.ts +++ b/packages/insomnia/src/models/helpers/project.ts @@ -1,5 +1,8 @@ import { database } from '../../common/database'; -import { initializeLocalBackendProjectAndMarkForSync, pushSnapshotOnInitialize } from '../../sync/vcs/initialize-backend-project'; +import { + initializeLocalBackendProjectAndMarkForSync, + pushSnapshotOnInitialize, +} from '../../sync/vcs/initialize-backend-project'; import type { VCS } from '../../sync/vcs/vcs'; import { insomniaFetch } from '../../ui/insomniaFetch'; import { invariant } from '../../utils/invariant'; @@ -7,10 +10,8 @@ import { isDefaultOrganizationProject, type Project, update as updateProject } f import type { Workspace } from '../workspace'; import { getOrCreateByParentId as getOrCreateWorkspaceMeta } from '../workspace-meta'; export const sortProjects = (projects: Project[]) => [ - ...projects.filter(p => isDefaultOrganizationProject(p)) - .sort((a, b) => a.name.localeCompare(b.name)), - ...projects.filter(p => !isDefaultOrganizationProject(p)) - .sort((a, b) => a.name.localeCompare(b.name)), + ...projects.filter(p => isDefaultOrganizationProject(p)).sort((a, b) => a.name.localeCompare(b.name)), + ...projects.filter(p => !isDefaultOrganizationProject(p)).sort((a, b) => a.name.localeCompare(b.name)), ]; export async function updateLocalProjectToRemote({ @@ -24,13 +25,16 @@ export async function updateLocalProjectToRemote({ sessionId: string; organizationId: string; }) { - const newCloudProject = await insomniaFetch<{ - id: string; - name: string; - } | { - error: string; - message?: string; - }>({ + const newCloudProject = await insomniaFetch< + | { + id: string; + name: string; + } + | { + error: string; + message?: string; + } + >({ path: `/v1/organizations/${organizationId}/team-projects`, method: 'POST', data: { @@ -53,9 +57,9 @@ export async function updateLocalProjectToRemote({ const updatedProject = await updateProject(project, { name: newCloudProject.name, remoteId: newCloudProject.id }); // For each workspace in the local project - const projectWorkspaces = (await database.find('Workspace', { + const projectWorkspaces = await database.find('Workspace', { parentId: updatedProject._id, - })); + }); for (const workspace of projectWorkspaces) { const workspaceMeta = await getOrCreateWorkspaceMeta(workspace._id); @@ -69,7 +73,10 @@ export async function updateLocalProjectToRemote({ await pushSnapshotOnInitialize({ vcs, workspace, project: updatedProject }); } } catch (e) { - console.warn('Failed to initialize sync on workspace. This will be retried when the workspace is opened on the app.', e); + console.warn( + 'Failed to initialize sync on workspace. This will be retried when the workspace is opened on the app.', + e, + ); // TODO: here we should show the try again dialog } } diff --git a/packages/insomnia/src/models/helpers/query-all-workspace-urls.ts b/packages/insomnia/src/models/helpers/query-all-workspace-urls.ts index 642a6fa505..c9b03c5aaa 100644 --- a/packages/insomnia/src/models/helpers/query-all-workspace-urls.ts +++ b/packages/insomnia/src/models/helpers/query-all-workspace-urls.ts @@ -11,7 +11,7 @@ export const queryAllWorkspaceUrls = async ( ): Promise => { const workspace = await models.workspace.getById(workspaceId); invariant(workspace, `Workspace ${workspaceId} not found`); - const docs = await db.withDescendants(workspace, reqType) as (Request | GrpcRequest)[]; + const docs = (await db.withDescendants(workspace, reqType)) as (Request | GrpcRequest)[]; const urls = docs .filter( d => diff --git a/packages/insomnia/src/models/mock-route.ts b/packages/insomnia/src/models/mock-route.ts index fef96f8a1c..1cd8491ef6 100644 --- a/packages/insomnia/src/models/mock-route.ts +++ b/packages/insomnia/src/models/mock-route.ts @@ -19,7 +19,7 @@ interface BaseMockRoute { statusCode: number; statusText: string; name: string; - mimeType: string;// response body type + mimeType: string; // response body type method: string; // used only for sending the testing request } @@ -38,9 +38,7 @@ export function init(): BaseMockRoute { }; } -export const isMockRoute = (model: Pick): model is MockRoute => ( - model.type === type -); +export const isMockRoute = (model: Pick): model is MockRoute => model.type === type; export function migrate(doc: MockRoute) { return doc; @@ -54,10 +52,7 @@ export function create(patch: Partial = {}) { return db.docCreate(type, patch); } -export function update( - mockRoute: MockRoute, - patch: Partial = {}, -) { +export function update(mockRoute: MockRoute, patch: Partial = {}) { return db.docUpdate(mockRoute, patch); } diff --git a/packages/insomnia/src/models/mock-server.ts b/packages/insomnia/src/models/mock-server.ts index cbdcbf9afd..9ba7f0f40a 100644 --- a/packages/insomnia/src/models/mock-server.ts +++ b/packages/insomnia/src/models/mock-server.ts @@ -29,9 +29,7 @@ export function init(): BaseMockServer { }; } -export const isMockServer = (model: Pick): model is MockServer => ( - model.type === type -); +export const isMockServer = (model: Pick): model is MockServer => model.type === type; export function migrate(doc: MockServer) { return doc; @@ -44,10 +42,7 @@ export function create(patch: Partial = {}) { return db.docCreate(type, patch); } -export async function getOrCreateForParentId( - workspaceId: string, - patch: Partial = {}, -) { +export async function getOrCreateForParentId(workspaceId: string, patch: Partial = {}) { const mockServer = await db.getWhere(type, { parentId: workspaceId, }); @@ -58,10 +53,7 @@ export async function getOrCreateForParentId( return mockServer; } -export function update( - mockServer: MockServer, - patch: Partial = {}, -) { +export function update(mockServer: MockServer, patch: Partial = {}) { return db.docUpdate(mockServer, patch); } diff --git a/packages/insomnia/src/models/o-auth-2-token.ts b/packages/insomnia/src/models/o-auth-2-token.ts index ca5a2e8678..a43a230a34 100644 --- a/packages/insomnia/src/models/o-auth-2-token.ts +++ b/packages/insomnia/src/models/o-auth-2-token.ts @@ -28,9 +28,7 @@ export interface BaseOAuth2Token { errorUri: string; } -export const isOAuth2Token = (model: Pick): model is OAuth2Token => ( - model.type === type -); +export const isOAuth2Token = (model: Pick): model is OAuth2Token => model.type === type; export function init(): BaseOAuth2Token { return { diff --git a/packages/insomnia/src/models/organization.ts b/packages/insomnia/src/models/organization.ts index 991521fc18..e3cf456340 100644 --- a/packages/insomnia/src/models/organization.ts +++ b/packages/insomnia/src/models/organization.ts @@ -15,25 +15,18 @@ export interface Organization { metadata: Metadata; } export const SCRATCHPAD_ORGANIZATION_ID = 'org_scratchpad'; -export const isScratchpadOrganizationId = (organizationId: string) => - organizationId === SCRATCHPAD_ORGANIZATION_ID; +export const isScratchpadOrganizationId = (organizationId: string) => organizationId === SCRATCHPAD_ORGANIZATION_ID; export const isPersonalOrganization = (organization: Organization) => organization.metadata.organizationType === 'personal'; -export const isOwnerOfOrganization = ({ - organization, - accountId, -}: { - organization: Organization; - accountId: string; -}) => +export const isOwnerOfOrganization = ({ organization, accountId }: { organization: Organization; accountId: string }) => organization.metadata.ownerAccountId === accountId; export const findPersonalOrganization = (organizations: Organization[], accountId: string) => { - return organizations.filter(isPersonalOrganization) - .find(organization => - isOwnerOfOrganization({ - organization, - accountId, - })); + return organizations.filter(isPersonalOrganization).find(organization => + isOwnerOfOrganization({ + organization, + accountId, + }), + ); }; diff --git a/packages/insomnia/src/models/plugin-data.ts b/packages/insomnia/src/models/plugin-data.ts index 4c7bdb368d..a4c4097346 100644 --- a/packages/insomnia/src/models/plugin-data.ts +++ b/packages/insomnia/src/models/plugin-data.ts @@ -19,9 +19,7 @@ interface BasePluginData { export type PluginData = BaseModel & BasePluginData; -export const isPluginData = (model: Pick): model is PluginData => ( - model.type === type -); +export const isPluginData = (model: Pick): model is PluginData => model.type === type; export function init(): BasePluginData { return { @@ -47,13 +45,13 @@ export async function upsertByKey(plugin: string, key: string, value: string) { const doc = await getByKey(plugin, key); return doc ? update(doc, { - value, - }) + value, + }) : create({ - plugin, - key, - value, - }); + plugin, + key, + value, + }); } export async function removeByKey(plugin: string, key: string) { diff --git a/packages/insomnia/src/models/project.ts b/packages/insomnia/src/models/project.ts index f13f579c6b..386f4df40f 100644 --- a/packages/insomnia/src/models/project.ts +++ b/packages/insomnia/src/models/project.ts @@ -12,9 +12,12 @@ export const canSync = false; export const SCRATCHPAD_PROJECT_ID = `${prefix}_scratchpad`; export const isScratchpadProject = (project: Pick) => project._id === SCRATCHPAD_PROJECT_ID; -export const isLocalProject = (project: Pick): project is LocalProject => project.remoteId === null; -export const isRemoteProject = (project: Pick): project is RemoteProject => !isLocalProject(project); -export const isGitProject = (project: Project): project is GitProject => 'gitRepositoryId' in project && project.gitRepositoryId !== null; +export const isLocalProject = (project: Pick): project is LocalProject => + project.remoteId === null; +export const isRemoteProject = (project: Pick): project is RemoteProject => + !isLocalProject(project); +export const isGitProject = (project: Project): project is GitProject => + 'gitRepositoryId' in project && project.gitRepositoryId !== null; export const projectHasSettings = (project: Pick) => !isScratchpadProject(project); interface CommonProject { @@ -38,13 +41,9 @@ export interface GitProject extends BaseModel, CommonProject { export type Project = LocalProject | RemoteProject | GitProject; -export const isProject = (model: Pick): model is Project => ( - model.type === type -); +export const isProject = (model: Pick): model is Project => model.type === type; -export const isProjectId = (id: string | null) => ( - id?.startsWith(`${prefix}_`) -); +export const isProjectId = (id: string | null) => id?.startsWith(`${prefix}_`); export function init(): Partial { return { @@ -93,7 +92,10 @@ export function isDefaultOrganizationProject(project: Project) { return project.remoteId?.startsWith('proj_team') || project.remoteId?.startsWith('proj_org'); } -export function getDefaultProjectStorageType(storageRules: StorageRules, project?: Project): 'local' | 'remote' | 'git' { +export function getDefaultProjectStorageType( + storageRules: StorageRules, + project?: Project, +): 'local' | 'remote' | 'git' { // When the project exist. That means the user open the settings modal if (project) { if (isGitProject(project)) { diff --git a/packages/insomnia/src/models/proto-directory.ts b/packages/insomnia/src/models/proto-directory.ts index 27fd10b68f..d1e2ef65a0 100644 --- a/packages/insomnia/src/models/proto-directory.ts +++ b/packages/insomnia/src/models/proto-directory.ts @@ -18,9 +18,7 @@ interface BaseProtoDirectory { export type ProtoDirectory = BaseModel & BaseProtoDirectory; -export const isProtoDirectory = (model: Pick): model is ProtoDirectory => ( - model.type === type -); +export const isProtoDirectory = (model: Pick): model is ProtoDirectory => model.type === type; export function init(): BaseProtoDirectory { return { diff --git a/packages/insomnia/src/models/proto-file.ts b/packages/insomnia/src/models/proto-file.ts index 76fa464bae..f510095439 100644 --- a/packages/insomnia/src/models/proto-file.ts +++ b/packages/insomnia/src/models/proto-file.ts @@ -18,9 +18,7 @@ interface BaseProtoFile { export type ProtoFile = BaseModel & BaseProtoFile; -export const isProtoFile = (model: Pick): model is ProtoFile => ( - model.type === type -); +export const isProtoFile = (model: Pick): model is ProtoFile => model.type === type; export function init(): BaseProtoFile { return { diff --git a/packages/insomnia/src/models/request-group-meta.ts b/packages/insomnia/src/models/request-group-meta.ts index 47c3e1c0db..17a943a2b7 100644 --- a/packages/insomnia/src/models/request-group-meta.ts +++ b/packages/insomnia/src/models/request-group-meta.ts @@ -17,9 +17,7 @@ interface BaseRequestGroupMeta { export type RequestGroupMeta = BaseModel & BaseRequestGroupMeta; -export const isRequestGroupMeta = (model: Pick): model is RequestGroupMeta => ( - model.type === type -); +export const isRequestGroupMeta = (model: Pick): model is RequestGroupMeta => model.type === type; export function init() { return { diff --git a/packages/insomnia/src/models/request-group.ts b/packages/insomnia/src/models/request-group.ts index 5c8e361852..40a80ad9b2 100644 --- a/packages/insomnia/src/models/request-group.ts +++ b/packages/insomnia/src/models/request-group.ts @@ -13,10 +13,7 @@ export const canDuplicate = true; export const canSync = true; // for those keys do not need to add in model init method -export const optionalKeys = [ - 'kvPairData', - 'environmentType', -]; +export const optionalKeys = ['kvPairData', 'environmentType']; interface BaseRequestGroup { name: string; description: string; @@ -33,9 +30,7 @@ interface BaseRequestGroup { export type RequestGroup = BaseModel & BaseRequestGroup; -export const isRequestGroup = (model: Pick): model is RequestGroup => ( - model.type === type -); +export const isRequestGroup = (model: Pick): model is RequestGroup => model.type === type; export function init(): BaseRequestGroup { return { @@ -99,9 +94,7 @@ export async function duplicate(requestGroup: RequestGroup, patch: Partial): model is RequestMeta => ( - model.type === type -); +export const isRequestMeta = (model: Pick): model is RequestMeta => model.type === type; export function init() { return { @@ -83,14 +81,13 @@ export async function updateOrCreateByParentId(parentId: string, patch: Partial< if (requestMeta) { return update(requestMeta, patch); } - const newPatch = Object.assign( - { - parentId, - }, - patch, - ); - return create(newPatch); - + const newPatch = Object.assign( + { + parentId, + }, + patch, + ); + return create(newPatch); } export function all() { diff --git a/packages/insomnia/src/models/request-version.ts b/packages/insomnia/src/models/request-version.ts index d7702ce3d8..bdbfab8904 100644 --- a/packages/insomnia/src/models/request-version.ts +++ b/packages/insomnia/src/models/request-version.ts @@ -35,9 +35,7 @@ const FIELDS_TO_IGNORE = [ 'name', ] as const; -export const isRequestVersion = (model: Pick): model is RequestVersion => ( - model.type === type -); +export const isRequestVersion = (model: Pick): model is RequestVersion => model.type === type; export function init() { return { @@ -78,9 +76,8 @@ export async function create(request: Request | WebSocketRequest | GrpcRequest) compressedRequest, }); } - // Re-use the latest version if not modified since - return latestRequestVersion; - + // Re-use the latest version if not modified since + return latestRequestVersion; } export function getLatestByParentId(parentId: string) { diff --git a/packages/insomnia/src/models/request.ts b/packages/insomnia/src/models/request.ts index 38ae260a9f..f3e1c17fd3 100644 --- a/packages/insomnia/src/models/request.ts +++ b/packages/insomnia/src/models/request.ts @@ -12,13 +12,9 @@ import type { AUTH_NTLM, AUTH_OAUTH_1, HAWK_ALGORITHM_SHA1, - HAWK_ALGORITHM_SHA256} from '../common/constants'; -import { - AUTH_BASIC, - CONTENT_TYPE_FORM_URLENCODED, - getContentTypeFromHeaders, - METHOD_GET, + HAWK_ALGORITHM_SHA256, } from '../common/constants'; +import { AUTH_BASIC, CONTENT_TYPE_FORM_URLENCODED, getContentTypeFromHeaders, METHOD_GET } from '../common/constants'; import { database as db } from '../common/database'; import type { OAuth1SignatureMethod } from '../network/o-auth-1/constants'; import { getOperationType } from '../utils/graph-ql'; @@ -34,7 +30,8 @@ export const prefix = 'req'; export const canDuplicate = true; export const canSync = true; -export type AuthTypes = 'none' +export type AuthTypes = + | 'none' | 'apikey' | 'oauth2' | 'oauth1' @@ -157,7 +154,8 @@ export interface AuthTypeNone { type: typeof AUTH_NONE; disabled?: boolean; } -export type RequestAuthentication = AuthTypeOAuth2 +export type RequestAuthentication = + | AuthTypeOAuth2 | AuthTypeBasic | AuthTypeBearer | AuthTypeDigest @@ -207,13 +205,20 @@ export const PATH_PARAMETER_REGEX = /\/:[^/?#:]+/g; export const getPathParametersFromUrl = (url: string): string[] => { // Find all path parameters in the URL. Path parameters are defined as segments of the URL that start with a colon. - const urlPathParameters = url.match(PATH_PARAMETER_REGEX)?.map(String).map(match => match.replace('/:', '')) || []; + const urlPathParameters = + url + .match(PATH_PARAMETER_REGEX) + ?.map(String) + .map(match => match.replace('/:', '')) || []; const uniqueUrlPathParameters = [...new Set(urlPathParameters)]; return uniqueUrlPathParameters; }; -export const getCombinedPathParametersFromUrl = (url: string, pathParameters: RequestPathParameter[]): RequestPathParameter[] => { +export const getCombinedPathParametersFromUrl = ( + url: string, + pathParameters: RequestPathParameter[], +): RequestPathParameter[] => { // Extract path parameters from the URL const urlPathParameters = getPathParametersFromUrl(url); @@ -233,8 +238,9 @@ export const getCombinedPathParametersFromUrl = (url: string, pathParameters: Re if (urlPathParameters) { // Filter out the unsaved URL path parameters unsavedUrlPathParameters = new Set( - urlPathParameters.filter(p => !savedPathParameters.map(p => p.name).includes(p)) - .map(p => ({ name: p, value: '' })) + urlPathParameters + .filter(p => !savedPathParameters.map(p => p.name).includes(p)) + .map(p => ({ name: p, value: '' })), ); } @@ -274,20 +280,14 @@ export interface BaseRequest { export type Request = BaseModel & BaseRequest; -export const isRequest = (model: Pick): model is Request => ( - model.type === type -); +export const isRequest = (model: Pick): model is Request => model.type === type; -export const isRequestId = (id?: string | null) => ( - id?.startsWith(`${prefix}_`) -); +export const isRequestId = (id?: string | null) => id?.startsWith(`${prefix}_`); -export const isEventStreamRequest = (model: Pick) => ( - isRequest(model) && model.headers?.find(h => h.name === 'Accept')?.value === 'text/event-stream' -); -export const isGraphqlSubscriptionRequest = (model: Pick) => ( - isRequest(model) && getOperationType(model) === OperationTypeNode.SUBSCRIPTION -); +export const isEventStreamRequest = (model: Pick) => + isRequest(model) && model.headers?.find(h => h.name === 'Accept')?.value === 'text/event-stream'; +export const isGraphqlSubscriptionRequest = (model: Pick) => + isRequest(model) && getOperationType(model) === OperationTypeNode.SUBSCRIPTION; export function init(): BaseRequest { return { @@ -416,12 +416,15 @@ function migrateBody(request: Request) { request.body = {}; } else { const rawBody: string = typeof request.body === 'string' ? request.body : ''; - request.body = typeof contentType !== 'string' ? { - text: rawBody, - } : { - mimeType: contentType.split(';')[0], - text: rawBody, - }; + request.body = + typeof contentType !== 'string' + ? { + text: rawBody, + } + : { + mimeType: contentType.split(';')[0], + text: rawBody, + }; } return request; diff --git a/packages/insomnia/src/models/response.ts b/packages/insomnia/src/models/response.ts index 784aafa77a..8c4218d271 100644 --- a/packages/insomnia/src/models/response.ts +++ b/packages/insomnia/src/models/response.ts @@ -56,9 +56,7 @@ export interface BaseResponse { export type Response = BaseModel & BaseResponse; -export const isResponse = (model: Pick): model is Response => ( - model.type === type -); +export const isResponse = (model: Pick): model is Response => model.type === type; export function init(): BaseResponse { return { @@ -147,11 +145,7 @@ export function remove(response: Response) { return db.remove(response); } -async function _findRecentForRequest( - requestId: string, - environmentId: string | null, - limit: number, -) { +async function _findRecentForRequest(requestId: string, environmentId: string | null, limit: number) { const query: Query = { parentId: requestId, }; @@ -165,10 +159,7 @@ async function _findRecentForRequest( return db.findMostRecentlyModified(type, query, limit); } -export async function getLatestForRequest( - requestId: string, - environmentId: string | null, -) { +export async function getLatestForRequest(requestId: string, environmentId: string | null) { const responses = await _findRecentForRequest(requestId, environmentId, 1); const response = responses[0] as Response | null | undefined; return response || null; @@ -230,7 +221,6 @@ export const getBodyStream = ( return fs.createReadStream(response?.bodyPath).pipe(zlib.createGunzip()); } return fs.createReadStream(response?.bodyPath); - }; export const readCurlResponse = async (options: { bodyPath?: string; bodyCompression?: Compression }) => { const readFailureMsg = '[main/curlBridgeAPI] failed to read response body message'; @@ -259,7 +249,9 @@ export const getBodyBuffer = async ( try { const rawBuffer = await fs.promises.readFile(response?.bodyPath); if (response?.bodyCompression === 'zip') { - return new Promise((resolve, reject) => zlib.gunzip(rawBuffer, (err, buffer) => err ? reject(err) : resolve(buffer))); + return new Promise((resolve, reject) => + zlib.gunzip(rawBuffer, (err, buffer) => (err ? reject(err) : resolve(buffer))), + ); } return rawBuffer; @@ -281,16 +273,18 @@ export function getTimeline(response: Response, showBody?: boolean) { const timelineString = rawBuffer.toString(); const isLegacyTimelineFormat = timelineString.startsWith('['); const timeline = isLegacyTimelineFormat - ? JSON.parse(timelineString) as ResponseTimelineEntry[] + ? (JSON.parse(timelineString) as ResponseTimelineEntry[]) : deserializeNDJSON(timelineString); - const body: ResponseTimelineEntry[] = showBody ? [ - { - name: 'DataOut', - timestamp: Date.now(), - value: fs.readFileSync(bodyPath).toString(), - }, - ] : []; + const body: ResponseTimelineEntry[] = showBody + ? [ + { + name: 'DataOut', + timestamp: Date.now(), + value: fs.readFileSync(bodyPath).toString(), + }, + ] + : []; const output = [...timeline, ...body]; return output; } catch (err) { diff --git a/packages/insomnia/src/models/runner-test-result.ts b/packages/insomnia/src/models/runner-test-result.ts index 41e28d8e85..4d3247a11b 100644 --- a/packages/insomnia/src/models/runner-test-result.ts +++ b/packages/insomnia/src/models/runner-test-result.ts @@ -40,9 +40,7 @@ export interface BaseRunnerTestResult { export type RunnerTestResult = BaseModel & BaseRunnerTestResult; -export const isRunnerTestResult = (model: Pick): model is RunnerTestResult => ( - model.type === type -); +export const isRunnerTestResult = (model: Pick): model is RunnerTestResult => model.type === type; export function init() { return { diff --git a/packages/insomnia/src/models/settings.ts b/packages/insomnia/src/models/settings.ts index daf61f7d0f..580af90001 100644 --- a/packages/insomnia/src/models/settings.ts +++ b/packages/insomnia/src/models/settings.ts @@ -1,8 +1,4 @@ -import { - getAppDefaultDarkTheme, - getAppDefaultLightTheme, - getAppDefaultTheme, -} from '../common/constants'; +import { getAppDefaultDarkTheme, getAppDefaultLightTheme, getAppDefaultTheme } from '../common/constants'; import { database as db } from '../common/database'; import * as hotkeys from '../common/hotkeys'; import { HttpVersions, type KeyboardShortcut, type Settings as BaseSettings, UpdateChannel } from '../common/settings'; @@ -17,9 +13,7 @@ export const canSync = false; export type ThemeSettings = Pick; -export const isSettings = (model: Pick): model is Settings => ( - model.type === type -); +export const isSettings = (model: Pick): model is Settings => model.type === type; export function init(): BaseSettings { return { @@ -116,7 +110,7 @@ export async function patch(patch: Partial) { } export async function getOrCreate() { - const results = await db.all(type) || []; + const results = (await db.all(type)) || []; if (results.length === 0) { return await create(); @@ -125,7 +119,7 @@ export async function getOrCreate() { } export async function get() { - const results = await db.all(type) || []; + const results = (await db.all(type)) || []; return results[0]; } @@ -137,13 +131,16 @@ function migrateEnsureHotKeys(settings: Settings): Settings { const defaultHotKeyRegistry = hotkeys.newDefaultRegistry(); // Remove any hotkeys that are no longer in the default registry - const hotKeyRegistry = (Object.keys(settings.hotKeyRegistry) as KeyboardShortcut[]).reduce((newHotKeyRegistry, key) => { - if (key in defaultHotKeyRegistry) { - newHotKeyRegistry[key] = settings.hotKeyRegistry[key]; - } + const hotKeyRegistry = (Object.keys(settings.hotKeyRegistry) as KeyboardShortcut[]).reduce( + (newHotKeyRegistry, key) => { + if (key in defaultHotKeyRegistry) { + newHotKeyRegistry[key] = settings.hotKeyRegistry[key]; + } - return newHotKeyRegistry; - }, {} as Settings['hotKeyRegistry']); + return newHotKeyRegistry; + }, + {} as Settings['hotKeyRegistry'], + ); settings.hotKeyRegistry = { ...defaultHotKeyRegistry, ...hotKeyRegistry }; return settings; diff --git a/packages/insomnia/src/models/stats.ts b/packages/insomnia/src/models/stats.ts index 4d68c2f415..5cb34c0561 100644 --- a/packages/insomnia/src/models/stats.ts +++ b/packages/insomnia/src/models/stats.ts @@ -30,9 +30,7 @@ export interface BaseStats { export type Stats = BaseModel & BaseStats; -export const isStats = (model: Pick): model is Stats => ( - model.type === type -); +export const isStats = (model: Pick): model is Stats => model.type === type; export function init(): BaseStats { return { @@ -61,24 +59,19 @@ export async function update(patch: Partial) { } export async function get() { - const results = await db.all(type) || []; + const results = (await db.all(type)) || []; if (results.length === 0) { return create(); } - return results[0]; - + return results[0]; } export function all() { return db.all(type) || []; } -export async function incrementRequestStats({ - createdRequests, - deletedRequests, - executedRequests, -}: Partial) { +export async function incrementRequestStats({ createdRequests, deletedRequests, executedRequests }: Partial) { const stats = await get(); await update({ ...(createdRequests && { diff --git a/packages/insomnia/src/models/unit-test-result.ts b/packages/insomnia/src/models/unit-test-result.ts index 1fdf32b75f..cc380bb286 100644 --- a/packages/insomnia/src/models/unit-test-result.ts +++ b/packages/insomnia/src/models/unit-test-result.ts @@ -19,9 +19,7 @@ export interface BaseUnitTestResult { export type UnitTestResult = BaseModel & BaseUnitTestResult; -export const isUnitTestResult = (model: Pick): model is UnitTestResult => ( - model.type === type -); +export const isUnitTestResult = (model: Pick): model is UnitTestResult => model.type === type; export function init() { return { diff --git a/packages/insomnia/src/models/unit-test-suite.ts b/packages/insomnia/src/models/unit-test-suite.ts index 7473c8c793..1eb868f49e 100644 --- a/packages/insomnia/src/models/unit-test-suite.ts +++ b/packages/insomnia/src/models/unit-test-suite.ts @@ -17,9 +17,7 @@ export interface BaseUnitTestSuite { export type UnitTestSuite = BaseModel & BaseUnitTestSuite; -export const isUnitTestSuite = (model: Pick): model is UnitTestSuite => ( - model.type === type -); +export const isUnitTestSuite = (model: Pick): model is UnitTestSuite => model.type === type; export function init() { return { diff --git a/packages/insomnia/src/models/unit-test.ts b/packages/insomnia/src/models/unit-test.ts index ae74d615cb..9a3b08b394 100644 --- a/packages/insomnia/src/models/unit-test.ts +++ b/packages/insomnia/src/models/unit-test.ts @@ -19,9 +19,7 @@ interface BaseUnitTest { export type UnitTest = BaseModel & BaseUnitTest; -export const isUnitTest = (model: Pick): model is UnitTest => ( - model.type === type -); +export const isUnitTest = (model: Pick): model is UnitTest => model.type === type; export function init() { return { diff --git a/packages/insomnia/src/models/user-session.ts b/packages/insomnia/src/models/user-session.ts index 2a919adb57..81c3aafab8 100644 --- a/packages/insomnia/src/models/user-session.ts +++ b/packages/insomnia/src/models/user-session.ts @@ -72,7 +72,7 @@ export async function patch(patch: Partial) { } export async function getOrCreate() { - const results = await db.all(type) || []; + const results = (await db.all(type)) || []; if (results.length === 0) { return await create(); @@ -81,7 +81,7 @@ export async function getOrCreate() { } export async function get() { - const results = await db.all(type) || []; + const results = (await db.all(type)) || []; return results[0]; } diff --git a/packages/insomnia/src/models/websocket-payload.ts b/packages/insomnia/src/models/websocket-payload.ts index bd062c4d05..da67ad6b39 100644 --- a/packages/insomnia/src/models/websocket-payload.ts +++ b/packages/insomnia/src/models/websocket-payload.ts @@ -19,13 +19,9 @@ export interface BaseWebSocketPayload { export type WebSocketPayload = BaseModel & BaseWebSocketPayload & { type: typeof type }; -export const isWebSocketPayload = (model: Pick): model is WebSocketPayload => ( - model.type === type -); +export const isWebSocketPayload = (model: Pick): model is WebSocketPayload => model.type === type; -export const isWebSocketPayloadId = (id: string | null) => ( - id?.startsWith(`${prefix}_`) -); +export const isWebSocketPayloadId = (id: string | null) => id?.startsWith(`${prefix}_`); export const init = (): BaseWebSocketPayload => ({ name: 'New Payload', @@ -45,10 +41,7 @@ export const create = (patch: Partial = {}) => { export const remove = (obj: WebSocketPayload) => database.remove(obj); -export const update = ( - obj: WebSocketPayload, - patch: Partial = {} -) => database.docUpdate(obj, patch); +export const update = (obj: WebSocketPayload, patch: Partial = {}) => database.docUpdate(obj, patch); export async function duplicate(request: WebSocketPayload, patch: Partial = {}) { // Only set name and "(Copy)" if the patch does diff --git a/packages/insomnia/src/models/websocket-request.ts b/packages/insomnia/src/models/websocket-request.ts index e1fd685adf..17630681b3 100644 --- a/packages/insomnia/src/models/websocket-request.ts +++ b/packages/insomnia/src/models/websocket-request.ts @@ -29,13 +29,9 @@ export interface BaseWebSocketRequest { export type WebSocketRequest = BaseModel & BaseWebSocketRequest & { type: typeof type }; -export const isWebSocketRequest = (model: Pick): model is WebSocketRequest => ( - model.type === type -); +export const isWebSocketRequest = (model: Pick): model is WebSocketRequest => model.type === type; -export const isWebSocketRequestId = (id?: string | null) => ( - id?.startsWith(`${prefix}_`) -); +export const isWebSocketRequestId = (id?: string | null) => id?.startsWith(`${prefix}_`); export const init = (): BaseWebSocketRequest => ({ name: 'New WebSocket Request', @@ -64,10 +60,7 @@ export const create = (patch: Partial = {}) => { export const remove = (obj: WebSocketRequest) => database.remove(obj); -export const update = ( - obj: WebSocketRequest, - patch: Partial = {} -) => database.docUpdate(obj, patch); +export const update = (obj: WebSocketRequest, patch: Partial = {}) => database.docUpdate(obj, patch); // This is duplicated (lol) from models/request.js export async function duplicate(request: WebSocketRequest, patch: Partial = {}) { diff --git a/packages/insomnia/src/models/websocket-response.ts b/packages/insomnia/src/models/websocket-response.ts index 1c430e3bb1..8dcb5c9e2b 100644 --- a/packages/insomnia/src/models/websocket-response.ts +++ b/packages/insomnia/src/models/websocket-response.ts @@ -37,9 +37,7 @@ export interface BaseWebSocketResponse { export type WebSocketResponse = BaseModel & BaseWebSocketResponse; -export const isWebSocketResponse = (model: Pick): model is WebSocketResponse => ( - model.type === type -); +export const isWebSocketResponse = (model: Pick): model is WebSocketResponse => model.type === type; export function init(): BaseWebSocketResponse { return { @@ -145,11 +143,7 @@ export async function create(patch: Partial = {}, maxResponse return db.docCreate(type, patch); } -async function _findRecentForRequest( - requestId: string, - environmentId: string | null, - limit: number, -) { +async function _findRecentForRequest(requestId: string, environmentId: string | null, limit: number) { const query: Query = { parentId: requestId, }; @@ -162,10 +156,7 @@ async function _findRecentForRequest( return db.findMostRecentlyModified(type, query, limit); } -export async function getLatestForRequest( - requestId: string, - environmentId: string | null, -) { +export async function getLatestForRequest(requestId: string, environmentId: string | null) { const responses = await _findRecentForRequest(requestId, environmentId, 1); const response = responses[0] as WebSocketResponse | null | undefined; return response || null; diff --git a/packages/insomnia/src/models/workspace-meta.ts b/packages/insomnia/src/models/workspace-meta.ts index 74f9747ed8..415c5062ba 100644 --- a/packages/insomnia/src/models/workspace-meta.ts +++ b/packages/insomnia/src/models/workspace-meta.ts @@ -23,9 +23,7 @@ export interface BaseWorkspaceMeta { export type WorkspaceMeta = BaseWorkspaceMeta & BaseModel; -export const isWorkspaceMeta = (model: Pick): model is WorkspaceMeta => ( - model.type === type -); +export const isWorkspaceMeta = (model: Pick): model is WorkspaceMeta => model.type === type; export function init(): BaseWorkspaceMeta { return { diff --git a/packages/insomnia/src/models/workspace.ts b/packages/insomnia/src/models/workspace.ts index d60c13bd41..7fcda4b6ee 100644 --- a/packages/insomnia/src/models/workspace.ts +++ b/packages/insomnia/src/models/workspace.ts @@ -31,25 +31,16 @@ export const WorkspaceScopeKeys = { export type Workspace = BaseModel & BaseWorkspace; -export const isWorkspace = (model: Pick): model is Workspace => ( - model.type === type -); +export const isWorkspace = (model: Pick): model is Workspace => model.type === type; -export const isDesign = (workspace: Pick) => ( - workspace.scope === WorkspaceScopeKeys.design -); +export const isDesign = (workspace: Pick) => workspace.scope === WorkspaceScopeKeys.design; -export const isCollection = (workspace: Pick) => ( - workspace.scope === WorkspaceScopeKeys.collection -); +export const isCollection = (workspace: Pick) => workspace.scope === WorkspaceScopeKeys.collection; -export const isMockServer = (workspace: Pick) => ( - workspace.scope === WorkspaceScopeKeys.mockServer -); +export const isMockServer = (workspace: Pick) => workspace.scope === WorkspaceScopeKeys.mockServer; -export const isEnvironment = (workspace: Pick) => ( - workspace.scope === WorkspaceScopeKeys.environment -); +export const isEnvironment = (workspace: Pick) => + workspace.scope === WorkspaceScopeKeys.environment; export const init = (): BaseWorkspace => ({ name: `New ${strings.collection.singular}`, @@ -146,10 +137,12 @@ type MigrationWorkspace = Merge design, unset => collection diff --git a/packages/insomnia/src/network/__tests__/authentication.test.ts b/packages/insomnia/src/network/__tests__/authentication.test.ts index 6d52a8e9c0..ebbb87749d 100644 --- a/packages/insomnia/src/network/__tests__/authentication.test.ts +++ b/packages/insomnia/src/network/__tests__/authentication.test.ts @@ -1,12 +1,7 @@ import { describe, expect, it } from 'vitest'; import { AUTH_API_KEY, AUTH_OAUTH_1 } from '../../common/constants'; -import { - _buildBearerHeader, - getAuthHeader, - getAuthObjectOrNull, - getAuthQueryParams, -} from '../authentication'; +import { _buildBearerHeader, getAuthHeader, getAuthObjectOrNull, getAuthQueryParams } from '../authentication'; describe('OAuth 1.0', () => { it('Does OAuth 1.0', async () => { @@ -165,8 +160,8 @@ describe('API Key', () => { }; const header = await getAuthHeader(request, 'https://insomnia.rest/'); expect(header).toEqual({ - 'name': 'x-api-key', - 'value': 'test', + name: 'x-api-key', + value: 'test', }); }); @@ -184,8 +179,8 @@ describe('API Key', () => { }; const header = await getAuthHeader(request, 'https://insomnia.rest/'); expect(header).toEqual({ - 'name': 'Cookie', - 'value': 'x-api-key=test', + name: 'Cookie', + value: 'x-api-key=test', }); }); }); @@ -201,8 +196,8 @@ describe('API Key', () => { const header = getAuthQueryParams(authentication, 'https://insomnia.rest/'); expect(header).toEqual({ - 'name': 'x-api-key', - 'value': 'test', + name: 'x-api-key', + value: 'test', }); }); }); diff --git a/packages/insomnia/src/network/__tests__/certificate.test.ts b/packages/insomnia/src/network/__tests__/certificate.test.ts index ec867bedc1..beec2e7113 100644 --- a/packages/insomnia/src/network/__tests__/certificate.test.ts +++ b/packages/insomnia/src/network/__tests__/certificate.test.ts @@ -4,7 +4,6 @@ import type { ClientCertificate } from '../../models/client-certificate'; import { filterClientCertificates } from '../certificate'; describe('filterClientCertificates', () => { - const requestUrl = 'https://www.example.com:1234'; const clientCertificatesWithMatchPort: ClientCertificate[] = [ { diff --git a/packages/insomnia/src/network/__tests__/is-url-matched-in-no-proxy-rule.test.ts b/packages/insomnia/src/network/__tests__/is-url-matched-in-no-proxy-rule.test.ts index a0dada4150..5770cc97d6 100644 --- a/packages/insomnia/src/network/__tests__/is-url-matched-in-no-proxy-rule.test.ts +++ b/packages/insomnia/src/network/__tests__/is-url-matched-in-no-proxy-rule.test.ts @@ -122,5 +122,4 @@ describe('isUrlMatchedInNoProxyRule - noProxyRule hostname and wildcard matches' const url = 'https://localhost/username/repo-name'; expect(isUrlMatchedInNoProxyRule(url, noProxyRule)).toBe(false); }); - }); diff --git a/packages/insomnia/src/network/__tests__/multipart.test.ts b/packages/insomnia/src/network/__tests__/multipart.test.ts index a570729367..ccb5059324 100644 --- a/packages/insomnia/src/network/__tests__/multipart.test.ts +++ b/packages/insomnia/src/network/__tests__/multipart.test.ts @@ -5,7 +5,6 @@ import { describe, expect, it } from 'vitest'; import { buildMultipart, DEFAULT_BOUNDARY } from '../../main/network/multipart'; describe('buildMultipart()', () => { - it('builds a simple request', async () => { const { filePath, boundary, contentLength } = await buildMultipart([ { diff --git a/packages/insomnia/src/network/__tests__/network.test.ts b/packages/insomnia/src/network/__tests__/network.test.ts index 1fc0041ff0..a6b2a7537d 100644 --- a/packages/insomnia/src/network/__tests__/network.test.ts +++ b/packages/insomnia/src/network/__tests__/network.test.ts @@ -21,7 +21,8 @@ import * as models from '../../models'; import * as networkUtils from '../network'; import { getSetCookiesFromResponseHeaders } from '../network'; -const getRenderedRequest = async (args: Parameters[0]) => (await getRenderedRequestAndContext(args)).request; +const getRenderedRequest = async (args: Parameters[0]) => + (await getRenderedRequestAndContext(args)).request; describe('sendCurlAndWriteTimeline()', () => { beforeEach(async () => { @@ -101,7 +102,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -180,7 +181,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -284,7 +285,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -348,7 +349,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -432,7 +433,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -497,7 +498,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -541,7 +542,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -584,7 +585,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -628,7 +629,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, settings, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -733,7 +734,7 @@ describe('sendCurlAndWriteTimeline()', () => { null, { ...settings, validateSSL: false }, '/tmp/res_id', - 'res_id' + 'res_id', ); const bodyBuffer = await models.response.getBodyBuffer(response); const body = JSON.parse(String(bodyBuffer)); @@ -783,12 +784,16 @@ describe('sendCurlAndWriteTimeline()', () => { parentId: workspace._id, }); const renderedRequest = await getRenderedRequest({ request }); - const responseV1 = await networkUtils.sendCurlAndWriteTimeline(renderedRequest, [], null, { - ...settings, - preferredHttpVersion: HttpVersions.V1_0, - }, + const responseV1 = await networkUtils.sendCurlAndWriteTimeline( + renderedRequest, + [], + null, + { + ...settings, + preferredHttpVersion: HttpVersions.V1_0, + }, '/tmp/res_id', - 'res_id' + 'res_id', ); expect(JSON.parse(String(await models.response.getBodyBuffer(responseV1))).options.HTTP_VERSION).toBe('V1_0'); expect(getHttpVersion(HttpVersions.V1_0).curlHttpVersion).toBe(CurlHttpVersion.V1_0); @@ -1030,7 +1035,10 @@ describe('getSetCookiesFromResponseHeaders', () => { expect(getSetCookiesFromResponseHeaders(headers)).toEqual(['monster']); }); it('gets two case-insenstive set-cookies', () => { - const headers = [{ name: 'Set-Cookie', value: 'monster' }, { name: 'set-cookie', value: 'mash' }]; + const headers = [ + { name: 'Set-Cookie', value: 'monster' }, + { name: 'set-cookie', value: 'mash' }, + ]; expect(getSetCookiesFromResponseHeaders(headers)).toEqual(['monster', 'mash']); }); }); @@ -1046,7 +1054,10 @@ describe('getCurrentUrl for tough-cookie', () => { expect(networkUtils.getCurrentUrl({ headerResults, finalUrl })).toEqual(finalUrl + '/cookies'); }); it('appends only last location to finalUrl', () => { - const headerResults = [{ headers: [{ name: 'Location', value: '/cookies' }] }, { headers: [{ name: 'location', value: '/biscuit' }] }]; + const headerResults = [ + { headers: [{ name: 'Location', value: '/cookies' }] }, + { headers: [{ name: 'location', value: '/biscuit' }] }, + ]; const finalUrl = 'http://mergemyshit.dev'; expect(networkUtils.getCurrentUrl({ headerResults, finalUrl })).toEqual(finalUrl + '/biscuit'); }); @@ -1055,23 +1066,59 @@ describe('getCurrentUrl for tough-cookie', () => { describe('getOrInheritHeaders', () => { it('should combine headers', () => { const requestGroups = [{ headers: [{ name: 'foo', value: 'bar' }] }, { headers: [{ name: 'baz', value: 'qux' }] }]; - const request = { headers: [{ name: 'foo', value: 'bar' }, { name: 'baz', value: 'qux' }] }; - expect(networkUtils.getOrInheritHeaders({ request, requestGroups })).toEqual([{ name: 'baz', value: 'qux, qux' }, { name: 'foo', value: 'bar, bar' }]); + const request = { + headers: [ + { name: 'foo', value: 'bar' }, + { name: 'baz', value: 'qux' }, + ], + }; + expect(networkUtils.getOrInheritHeaders({ request, requestGroups })).toEqual([ + { name: 'baz', value: 'qux, qux' }, + { name: 'foo', value: 'bar, bar' }, + ]); }); it('should use last header casing', () => { const requestGroups = [{ headers: [{ name: 'x-foo', value: 'bar' }] }]; const request = { headers: [{ name: 'X-Foo', value: 'baz' }] }; - expect(networkUtils.getOrInheritHeaders({ request, requestGroups })).toEqual([{ name: 'X-Foo', value: 'bar, baz' }]); + expect(networkUtils.getOrInheritHeaders({ request, requestGroups })).toEqual([ + { name: 'X-Foo', value: 'bar, baz' }, + ]); }); it('should not combine special headers', () => { - const requestGroups = [{ headers: [{ name: 'content-type', value: 'application/json' }, { name: 'Connection', value: 'close' }] }]; - const request = { headers: [{ name: 'Content-Type', value: 'text/plain' }, { name: 'connection', value: 'keep-alive' }] }; - expect(networkUtils.getOrInheritHeaders({ request, requestGroups })).toEqual([{ name: 'connection', value: 'keep-alive' }, { name: 'Content-Type', value: 'text/plain' }]); + const requestGroups = [ + { + headers: [ + { name: 'content-type', value: 'application/json' }, + { name: 'Connection', value: 'close' }, + ], + }, + ]; + const request = { + headers: [ + { name: 'Content-Type', value: 'text/plain' }, + { name: 'connection', value: 'keep-alive' }, + ], + }; + expect(networkUtils.getOrInheritHeaders({ request, requestGroups })).toEqual([ + { name: 'connection', value: 'keep-alive' }, + { name: 'Content-Type', value: 'text/plain' }, + ]); }); it('should not allow an empty header name', () => { - const requestGroups = [{ headers: [{ name: '', value: 'bar' }, { name: ' ', value: 'foo' }] }]; - const request = { headers: [{ name: '', value: 'baz' }, { name: ' ', value: 'qux' }] }; + const requestGroups = [ + { + headers: [ + { name: '', value: 'bar' }, + { name: ' ', value: 'foo' }, + ], + }, + ]; + const request = { + headers: [ + { name: '', value: 'baz' }, + { name: ' ', value: 'qux' }, + ], + }; expect(networkUtils.getOrInheritHeaders({ request, requestGroups })).toEqual([]); }); - }); diff --git a/packages/insomnia/src/network/__tests__/parse-header-strings.test.ts b/packages/insomnia/src/network/__tests__/parse-header-strings.test.ts index 7277af85b9..be834e65e9 100644 --- a/packages/insomnia/src/network/__tests__/parse-header-strings.test.ts +++ b/packages/insomnia/src/network/__tests__/parse-header-strings.test.ts @@ -11,19 +11,35 @@ describe('parseHeaderStrings', () => { it('should disable expect and transfer-encoding with body', () => { const req = { authentication: {}, body: {}, headers: [] }; - expect(parseHeaderStrings({ req, requestBody: 'test' })).toEqual(['Expect:', 'Transfer-Encoding:', 'Accept: */*', 'Accept-Encoding:', 'content-type:']); + expect(parseHeaderStrings({ req, requestBody: 'test' })).toEqual([ + 'Expect:', + 'Transfer-Encoding:', + 'Accept: */*', + 'Accept-Encoding:', + 'content-type:', + ]); }); it('should add boundary with multipart body path', () => { const req = { authentication: {}, body: { mimeType: CONTENT_TYPE_FORM_DATA }, headers: [] }; - expect(parseHeaderStrings({ req, requestBodyPath: '/tmp/x.z' })).toEqual(['Expect:', 'Transfer-Encoding:', 'Content-Type: multipart/form-data; boundary=X-INSOMNIA-BOUNDARY', 'Accept: */*', 'Accept-Encoding:']); + expect(parseHeaderStrings({ req, requestBodyPath: '/tmp/x.z' })).toEqual([ + 'Expect:', + 'Transfer-Encoding:', + 'Content-Type: multipart/form-data; boundary=X-INSOMNIA-BOUNDARY', + 'Accept: */*', + 'Accept-Encoding:', + ]); }); it('should sign with aws iam', () => { - const req = { authentication: { - sessionToken: 'someTokenSomethingSomething', - type: AUTH_AWS_IAM, - }, body: {}, headers: [] }; + const req = { + authentication: { + sessionToken: 'someTokenSomethingSomething', + type: AUTH_AWS_IAM, + }, + body: {}, + headers: [], + }; const [host, token, date, authorization] = parseHeaderStrings({ req, finalUrl: 'http://x.y' }); expect(host).toBe('Host: x.y'); expect(token).toContain('X-Amz-Security-Token: someTokenSomethingSomething'); diff --git a/packages/insomnia/src/network/__tests__/url-matches-cert-host.test.ts b/packages/insomnia/src/network/__tests__/url-matches-cert-host.test.ts index e9c413e5b5..8273ec7c55 100644 --- a/packages/insomnia/src/network/__tests__/url-matches-cert-host.test.ts +++ b/packages/insomnia/src/network/__tests__/url-matches-cert-host.test.ts @@ -4,7 +4,6 @@ import { urlMatchesCertHost } from '../url-matches-cert-host'; describe('urlMatchesCertHost', () => { describe('when the certificate host has no wildcard', () => { - it('should return false if the requested host does not match the certificate host', () => { const requestUrl = 'https://www.example.org'; const certificateHost = 'https://www.example.com'; diff --git a/packages/insomnia/src/network/authentication.ts b/packages/insomnia/src/network/authentication.ts index 73d7fa5484..fc05fade3f 100644 --- a/packages/insomnia/src/network/authentication.ts +++ b/packages/insomnia/src/network/authentication.ts @@ -89,8 +89,7 @@ export async function getAuthHeader(renderedRequest: RenderedRequest, url: strin value: oAuth1Token.Authorization, }; } - return; - + return; } if (authentication.type === AUTH_HAWK) { @@ -131,9 +130,7 @@ export async function getAuthHeader(renderedRequest: RenderedRequest, url: strin if (parsedAdditionalClaims) { if (typeof parsedAdditionalClaims !== 'object') { - throw new Error( - `additional-claims must be an object received: '${typeof parsedAdditionalClaims}' instead`, - ); + throw new Error(`additional-claims must be an object received: '${typeof parsedAdditionalClaims}' instead`); } } const generator = (await import('httplease-asap')).createAuthHeaderGenerator({ @@ -189,6 +186,7 @@ export const _buildBearerHeader = (accessToken: string, prefix?: string) => { return header; }; -export const isAuthEnabled = (auth?: RequestAuthentication | {}) => (auth && 'disabled' in auth) ? auth.disabled !== true : true; +export const isAuthEnabled = (auth?: RequestAuthentication | {}) => + auth && 'disabled' in auth ? auth.disabled !== true : true; export const getAuthObjectOrNull = (auth?: RequestAuthentication | {} | null): RequestAuthentication | null => - (!auth || Object.keys(auth).length === 0 || !('type' in auth)) ? null : auth; + !auth || Object.keys(auth).length === 0 || !('type' in auth) ? null : auth; diff --git a/packages/insomnia/src/network/basic-auth/get-header.ts b/packages/insomnia/src/network/basic-auth/get-header.ts index 4439aa96f9..e618e19fbc 100644 --- a/packages/insomnia/src/network/basic-auth/get-header.ts +++ b/packages/insomnia/src/network/basic-auth/get-header.ts @@ -1,10 +1,6 @@ import type { RequestHeader } from '../../models/request'; -export function getBasicAuthHeader( - username?: string | null, - password?: string | null, - encoding = 'utf8', -) { +export function getBasicAuthHeader(username?: string | null, password?: string | null, encoding = 'utf8') { const name = 'Authorization'; const header = `${username || ''}:${password || ''}`; // @ts-expect-error -- TSCONVERSION appears to be a genuine error diff --git a/packages/insomnia/src/network/bearer-auth/__tests__/get-header.test.ts b/packages/insomnia/src/network/bearer-auth/__tests__/get-header.test.ts index 4fd64edf66..968a4807aa 100644 --- a/packages/insomnia/src/network/bearer-auth/__tests__/get-header.test.ts +++ b/packages/insomnia/src/network/bearer-auth/__tests__/get-header.test.ts @@ -3,7 +3,6 @@ import { describe, expect, it } from 'vitest'; import { getBearerAuthHeader } from '../get-header'; describe('getBearerAuthHeader()', () => { - it('succeed with token and prefix', () => { const header = getBearerAuthHeader('token', 'prefix'); expect(header).toEqual({ diff --git a/packages/insomnia/src/network/certificate.ts b/packages/insomnia/src/network/certificate.ts index 02904dacf8..a9d1c3bdb0 100644 --- a/packages/insomnia/src/network/certificate.ts +++ b/packages/insomnia/src/network/certificate.ts @@ -2,11 +2,19 @@ import type { ClientCertificate } from '../models/client-certificate'; import { setDefaultProtocol } from '../utils/url/protocol'; import { urlMatchesCertHost } from './url-matches-cert-host'; -export function filterClientCertificates(clientCertificates: ClientCertificate[], requestUrl: string, protocol?: string) { - const res = clientCertificates.filter(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, protocol), requestUrl, true)); +export function filterClientCertificates( + clientCertificates: ClientCertificate[], + requestUrl: string, + protocol?: string, +) { + const res = clientCertificates.filter( + c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, protocol), requestUrl, true), + ); // If didn't get a matching certificate at the first time, ignore the port check and try again if (!res.length) { - return clientCertificates.filter(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, protocol), requestUrl, false)); + return clientCertificates.filter( + c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, protocol), requestUrl, false), + ); } return res; } diff --git a/packages/insomnia/src/network/concurrency.ts b/packages/insomnia/src/network/concurrency.ts index 5354b6c0b0..d9ed06bd50 100644 --- a/packages/insomnia/src/network/concurrency.ts +++ b/packages/insomnia/src/network/concurrency.ts @@ -10,53 +10,61 @@ import type { Settings } from '../models/settings'; import { cancellableExecution } from './cancellation'; export interface ExecuteScriptContext { - request: Request; - environment: { - id: string; - name: string; - data: object; - }; - baseEnvironment: { - id: string; - name: string; - data: object; - }; - clientCertificates: ClientCertificate[]; - settings: Settings; - globals?: object; - cookieJar: CookieJar; - requestTestResults?: RequestTestResult[]; + request: Request; + environment: { + id: string; + name: string; + data: object; + }; + baseEnvironment: { + id: string; + name: string; + data: object; + }; + clientCertificates: ClientCertificate[]; + settings: Settings; + globals?: object; + cookieJar: CookieJar; + requestTestResults?: RequestTestResult[]; } export interface TransformedExecuteScriptContext { - error?: string; - request: Request; - environment: Environment; - baseEnvironment: Environment; - clientCertificates: ClientCertificate[]; - settings: Settings; - globals?: Environment; - cookieJar: CookieJar; - requestTestResults?: RequestTestResult[]; - userUploadEnvironment?: UserUploadEnvironment; - parentFolders: { id: string; name: string; environment: Record }[]; + error?: string; + request: Request; + environment: Environment; + baseEnvironment: Environment; + clientCertificates: ClientCertificate[]; + settings: Settings; + globals?: Environment; + cookieJar: CookieJar; + requestTestResults?: RequestTestResult[]; + userUploadEnvironment?: UserUploadEnvironment; + parentFolders: { id: string; name: string; environment: Record }[]; } interface Task { - script: string; - context: RequestContext; + script: string; + context: RequestContext; } const q: queueAsPromised = fastq.promise(asyncWorker, 1); async function asyncWorker(arg: Task): Promise { - const timeoutValue = arg.context.settings.timeout || 30000; - const timeoutPromise = new Promise<{ error: string }>(resolve => setTimeout(resolve, timeoutValue, { error: `Executing script timeout: ${timeoutValue}` })); - const executionPromise = Promise.race([window.main.hiddenBrowserWindow.runScript({ script: arg.script, context: arg.context }), timeoutPromise]); - const result = await cancellableExecution({ id: arg.context.request._id, fn: executionPromise }); - return result; + const timeoutValue = arg.context.settings.timeout || 30000; + const timeoutPromise = new Promise<{ error: string }>(resolve => + setTimeout(resolve, timeoutValue, { error: `Executing script timeout: ${timeoutValue}` }), + ); + const executionPromise = Promise.race([ + window.main.hiddenBrowserWindow.runScript({ script: arg.script, context: arg.context }), + timeoutPromise, + ]); + const result = await cancellableExecution({ id: arg.context.request._id, fn: executionPromise }); + return result; } -export const runScriptConcurrently = async (options: { script: string; context: RequestContext }): Promise => { - return await q.push(options); +export const runScriptConcurrently = async (options: { + script: string; + context: RequestContext; +}): Promise => { + return await q.push(options); }; diff --git a/packages/insomnia/src/network/grpc/__tests__/write-proto-file.test.ts b/packages/insomnia/src/network/grpc/__tests__/write-proto-file.test.ts index 582af09870..278230f652 100644 --- a/packages/insomnia/src/network/grpc/__tests__/write-proto-file.test.ts +++ b/packages/insomnia/src/network/grpc/__tests__/write-proto-file.test.ts @@ -107,12 +107,7 @@ describe('writeProtoFile', () => { // Act const result = await writeProtoFile(pf); // Assert - const expectedRootDir = path.join( - tmpDirPath, - 'insomnia-grpc', - `${pd._id}.${pd.modified}`, - pd.name, - ); + const expectedRootDir = path.join(tmpDirPath, 'insomnia-grpc', `${pd._id}.${pd.modified}`, pd.name); const expectedFilePath = pf.name; const expectedFullPath = path.join(expectedRootDir, expectedFilePath); expect(result.filePath).toEqual(expectedFilePath); @@ -149,12 +144,7 @@ describe('writeProtoFile', () => { // Act const result = await writeProtoFile(pfNested); // Assert - const expectedRootDir = path.join( - tmpDirPath, - 'insomnia-grpc', - `${pdRoot._id}.${pdRoot.modified}`, - pdRoot.name, - ); + const expectedRootDir = path.join(tmpDirPath, 'insomnia-grpc', `${pdRoot._id}.${pdRoot.modified}`, pdRoot.name); const expectedNestedDir = path.join(expectedRootDir, pdNested.name); const expectedFilePath = { root: pfRoot.name, @@ -202,12 +192,7 @@ describe('writeProtoFile', () => { // Act const result = await writeProtoFile(pfNested); // Assert - const expectedRootDir = path.join( - tmpDirPath, - 'insomnia-grpc', - `${pdRoot._id}.${pdRoot.modified}`, - pdRoot.name, - ); + const expectedRootDir = path.join(tmpDirPath, 'insomnia-grpc', `${pdRoot._id}.${pdRoot.modified}`, pdRoot.name); const expectedNestedDir = path.join(expectedRootDir, pdNested.name); const expectedFilePath = { root: pfRoot.name, diff --git a/packages/insomnia/src/network/grpc/write-proto-file.ts b/packages/insomnia/src/network/grpc/write-proto-file.ts index 8e2f91ecf9..db47e1b1ba 100644 --- a/packages/insomnia/src/network/grpc/write-proto-file.ts +++ b/packages/insomnia/src/network/grpc/write-proto-file.ts @@ -24,16 +24,20 @@ const recursiveWriteProtoDirectory = async ( fs.mkdirSync(dirPath, { recursive: true }); // Get and write proto files const files = descendants.filter(isProtoFile).filter(f => f.parentId === dir._id); - await Promise.all(files.map(protoFile => { - const fullPath = path.join(dirPath, protoFile.name); - if (fs.existsSync(fullPath)) { - return; - } - fs.promises.writeFile(fullPath, protoFile.protoText); - })); + await Promise.all( + files.map(protoFile => { + const fullPath = path.join(dirPath, protoFile.name); + if (fs.existsSync(fullPath)) { + return; + } + fs.promises.writeFile(fullPath, protoFile.protoText); + }), + ); // Get and write subdirectories const createdDirs = await Promise.all( - descendants.filter(f => isProtoDirectory(f) && f.parentId === dir._id).map(f => recursiveWriteProtoDirectory(f, descendants, dirPath)), + descendants + .filter(f => isProtoDirectory(f) && f.parentId === dir._id) + .map(f => recursiveWriteProtoDirectory(f, descendants, dirPath)), ); return [dirPath, ...createdDirs.flat()]; }; @@ -57,10 +61,13 @@ export const writeProtoFile = async (protoFile: ProtoFile): Promise if (!ancestors.find(isWorkspace) || !rootAncestorProtoDirectory) { // should never happen return { - filePath: path.join(...ancestorDirectories - .map(f => f.name) - .reverse() - .slice(1), protoFile.name), + filePath: path.join( + ...ancestorDirectories + .map(f => f.name) + .reverse() + .slice(1), + protoFile.name, + ), dirs: [], }; } @@ -76,30 +83,32 @@ export const writeProtoFile = async (protoFile: ProtoFile): Promise ), ); return { - filePath: path.join(...ancestorDirectories - .map(f => f.name) - .reverse() - .slice(1), protoFile.name), + filePath: path.join( + ...ancestorDirectories + .map(f => f.name) + .reverse() + .slice(1), + protoFile.name, + ), dirs: treeRootDirs, }; } - // Write single file - // Create temp folder - const rootDir = path.join(os.tmpdir(), 'insomnia-grpc'); - fs.mkdirSync(rootDir, { recursive: true }); + // Write single file + // Create temp folder + const rootDir = path.join(os.tmpdir(), 'insomnia-grpc'); + fs.mkdirSync(rootDir, { recursive: true }); - const filePath = `${protoFile._id}.${protoFile.modified}.proto`; - const result = { - filePath, - dirs: [rootDir], - }; - // Check if file already exists - const fullPath = path.join(rootDir, filePath); - if (fs.existsSync(fullPath)) { - return result; - } - // Write file - await fs.promises.writeFile(fullPath, protoFile.protoText); + const filePath = `${protoFile._id}.${protoFile.modified}.proto`; + const result = { + filePath, + dirs: [rootDir], + }; + // Check if file already exists + const fullPath = path.join(rootDir, filePath); + if (fs.existsSync(fullPath)) { return result; - + } + // Write file + await fs.promises.writeFile(fullPath, protoFile.protoText); + return result; }; diff --git a/packages/insomnia/src/network/is-url-matched-in-no-proxy-rule.ts b/packages/insomnia/src/network/is-url-matched-in-no-proxy-rule.ts index c4aa02c282..59abb3f717 100644 --- a/packages/insomnia/src/network/is-url-matched-in-no-proxy-rule.ts +++ b/packages/insomnia/src/network/is-url-matched-in-no-proxy-rule.ts @@ -19,7 +19,7 @@ function parseNoProxyZone(zone: string) { function matchesHostname(hostname: string, noProxyZoneHostname: string) { const wildcardNeedle = noProxyZoneHostname.startsWith('.*.') ? noProxyZoneHostname.slice(2) : noProxyZoneHostname; const isMatchedAt = hostname.indexOf(wildcardNeedle); - return (isMatchedAt > -1 && (isMatchedAt === hostname.length - wildcardNeedle.length)); + return isMatchedAt > -1 && isMatchedAt === hostname.length - wildcardNeedle.length; } export function isUrlMatchedInNoProxyRule(url: string | undefined, noProxyRule: any) { @@ -32,7 +32,7 @@ export function isUrlMatchedInNoProxyRule(url: string | undefined, noProxyRule: } const port = uri.port || (uri.protocol === 'https:' ? '443' : '80'); // TODO: remove non-null assertion - + const hostname = formatHostname(uri.hostname!); const noProxyList = noProxyRule.split(','); @@ -43,7 +43,7 @@ export function isUrlMatchedInNoProxyRule(url: string | undefined, noProxyRule: } const hostnameMatched = matchesHostname(hostname, noProxyZone.hostname); if (noProxyZone.hasPort) { - return (port === noProxyZone.port) && hostnameMatched; + return port === noProxyZone.port && hostnameMatched; } return hostnameMatched; }); diff --git a/packages/insomnia/src/network/network.ts b/packages/insomnia/src/network/network.ts index 88d71848f8..8d38645308 100644 --- a/packages/insomnia/src/network/network.ts +++ b/packages/insomnia/src/network/network.ts @@ -3,30 +3,40 @@ import fs from 'fs'; import orderedJSON from 'json-order'; import { join as pathJoin } from 'path'; -import type { ExecutionOption, RequestContext, RequestTestResult } from '../../../insomnia-scripting-environment/src/objects'; +import type { + ExecutionOption, + RequestContext, + RequestTestResult, +} from '../../../insomnia-scripting-environment/src/objects'; import { SINGLE_VALUE_HEADERS } from '../common/common-headers'; import { JSON_ORDER_PREFIX, JSON_ORDER_SEPARATOR } from '../common/constants'; import { database as db } from '../common/database'; -import { - generateId, - getContentTypeHeader, - getLocationHeader, - getSetCookieHeaders, -} from '../common/misc'; -import { - getRenderedRequestAndContext, -} from '../common/render'; +import { generateId, getContentTypeHeader, getLocationHeader, getSetCookieHeaders } from '../common/misc'; +import { getRenderedRequestAndContext } from '../common/render'; import { ascendingFirstIndexStringSort } from '../common/sorting'; import type { HeaderResult, ResponsePatch, ResponseTimelineEntry } from '../main/network/libcurl-promise'; import * as models from '../models'; import type { CaCertificate } from '../models/ca-certificate'; import type { ClientCertificate } from '../models/client-certificate'; import type { Cookie, CookieJar } from '../models/cookie-jar'; -import { type Environment, EnvironmentType, getKVPairFromData, type UserUploadEnvironment, vaultEnvironmentPath } from '../models/environment'; +import { + type Environment, + EnvironmentType, + getKVPairFromData, + type UserUploadEnvironment, + vaultEnvironmentPath, +} from '../models/environment'; import type { MockRoute } from '../models/mock-route'; import type { MockServer } from '../models/mock-server'; import { isProject, type Project } from '../models/project'; -import { type BaseRequest, isRequest, type Request, type RequestAuthentication, type RequestHeader, type RequestParameter } from '../models/request'; +import { + type BaseRequest, + isRequest, + type Request, + type RequestAuthentication, + type RequestHeader, + type RequestParameter, +} from '../models/request'; import { isRequestGroup, type RequestGroup } from '../models/request-group'; import type { Settings } from '../models/settings'; import type { WebSocketRequest } from '../models/websocket-request'; @@ -39,24 +49,28 @@ import { maskOrDecryptVaultDataIfNecessary } from '../templating/utils'; import { defaultSendActionRuntime, type SendActionRuntime } from '../ui/routes/request'; import { invariant } from '../utils/invariant'; import { serializeNDJSON } from '../utils/ndjson'; -import { - buildQueryStringFromParams, - joinUrlAndQueryString, - smartEncodeUrl, -} from '../utils/url/querystring'; +import { buildQueryStringFromParams, joinUrlAndQueryString, smartEncodeUrl } from '../utils/url/querystring'; import { getAuthHeader, getAuthObjectOrNull, getAuthQueryParams, isAuthEnabled } from './authentication'; import { cancellableCurlRequest, cancellableRunScript } from './cancellation'; import { filterClientCertificates } from './certificate'; import { runScriptConcurrently, type TransformedExecuteScriptContext } from './concurrency'; import { addSetCookiesToToughCookieJar } from './set-cookie-util'; -export const getOrInheritAuthentication = ({ request, requestGroups }: { request: Request | WebSocketRequest; requestGroups: RequestGroup[] }): RequestAuthentication | {} => { +export const getOrInheritAuthentication = ({ + request, + requestGroups, +}: { + request: Request | WebSocketRequest; + requestGroups: RequestGroup[]; +}): RequestAuthentication | {} => { const hasValidAuth = getAuthObjectOrNull(request.authentication) && isAuthEnabled(request.authentication); if (hasValidAuth) { return request.authentication; } const hasParentFolders = requestGroups.length > 0; - const closestParentFolderWithAuth = requestGroups.find(({ authentication }) => getAuthObjectOrNull(authentication) && isAuthEnabled(authentication)); + const closestParentFolderWithAuth = requestGroups.find( + ({ authentication }) => getAuthObjectOrNull(authentication) && isAuthEnabled(authentication), + ); const closestAuth = getAuthObjectOrNull(closestParentFolderWithAuth?.authentication); const shouldCheckFolderAuth = hasParentFolders && closestAuth; if (shouldCheckFolderAuth) { @@ -66,7 +80,13 @@ export const getOrInheritAuthentication = ({ request, requestGroups }: { request // if no auth is specified on request or folders, default to none return { type: 'none' }; }; -export function getOrInheritHeaders({ request, requestGroups }: { request: Pick; requestGroups: Pick[] }): RequestHeader[] { +export function getOrInheritHeaders({ + request, + requestGroups, +}: { + request: Pick; + requestGroups: Pick[]; +}): RequestHeader[] { const httpHeaders = new Map(); const originalCaseMap = new Map(); // parent folders, then child folders, then request @@ -91,7 +111,9 @@ export function getOrInheritHeaders({ request, requestGroups }: { request: Pick< } httpHeaders.set(normalizedCase, value); }); - return Array.from(httpHeaders.entries()).sort(ascendingFirstIndexStringSort).map(([name, value]) => ({ name: originalCaseMap.get(name)!, value })); + return Array.from(httpHeaders.entries()) + .sort(ascendingFirstIndexStringSort) + .map(([name, value]) => ({ name: originalCaseMap.get(name)!, value })); } // (only used for getOAuth2 token) Intended to gather all required database objects and initialize ids export const fetchRequestGroupData = async (requestGroupId: string) => { @@ -113,8 +135,8 @@ export const fetchRequestGroupData = async (requestGroupId: string) => { // NOTE: parent folders wont be checked in here since we only use it for oauth2 requests right now, so they are discarded in that code path // fallback to base environment const activeEnvironmentId = workspaceMeta.activeEnvironmentId; - const activeEnvironment = activeEnvironmentId && await models.environment.getById(activeEnvironmentId); - const environment = activeEnvironment || await models.environment.getOrCreateForParentId(workspace._id); + const activeEnvironment = activeEnvironmentId && (await models.environment.getById(activeEnvironmentId)); + const environment = activeEnvironment || (await models.environment.getOrCreateForParentId(workspace._id)); invariant(environment, 'failed to find environment ' + activeEnvironmentId); const settings = await models.settings.get(); @@ -122,7 +144,10 @@ export const fetchRequestGroupData = async (requestGroupId: string) => { const clientCertificates = await models.clientCertificate.findByParentId(workspaceId); const caCert = await models.caCertificate.findByParentId(workspaceId); const responseId = generateId('res'); - const responsesDir = pathJoin((process.type === 'renderer' ? window : require('electron')).app.getPath('userData'), 'responses'); + const responsesDir = pathJoin( + (process.type === 'renderer' ? window : require('electron')).app.getPath('userData'), + 'responses', + ); const timelinePath = pathJoin(responsesDir, responseId + '.timeline'); return { environment, settings, clientCertificates, caCert, activeEnvironmentId, timelinePath, responseId }; }; @@ -130,14 +155,17 @@ export const fetchRequestGroupData = async (requestGroupId: string) => { export const fetchRequestData = async (requestId: string) => { const request = await models.request.getById(requestId); invariant(request, 'failed to find request ' + requestId); - const ancestors = await db.withAncestors(request, [ - models.request.type, - models.requestGroup.type, - models.workspace.type, - models.project.type, - models.mockRoute.type, - models.mockServer.type, - ]); + const ancestors = await db.withAncestors( + request, + [ + models.request.type, + models.requestGroup.type, + models.workspace.type, + models.project.type, + models.mockRoute.type, + models.mockServer.type, + ], + ); const workspaceDoc = ancestors.find(isWorkspace); invariant(workspaceDoc?._id, 'failed to find workspace'); const workspaceId = workspaceDoc._id; @@ -147,9 +175,9 @@ export const fetchRequestData = async (requestId: string) => { const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspace._id); // fallback to base environment const activeEnvironmentId = workspaceMeta.activeEnvironmentId; - const activeEnvironment = activeEnvironmentId && await models.environment.getById(activeEnvironmentId); + const activeEnvironment = activeEnvironmentId && (await models.environment.getById(activeEnvironmentId)); // no active environment in workspaceMeta, fallback to workspace root environment as active environment - const environment = activeEnvironment || await models.environment.getOrCreateForParentId(workspace._id); + const environment = activeEnvironment || (await models.environment.getOrCreateForParentId(workspace._id)); invariant(environment, 'failed to find environment ' + activeEnvironmentId); const baseEnvironment = await models.environment.getOrCreateForParentId(workspaceId); @@ -157,7 +185,7 @@ export const fetchRequestData = async (requestId: string) => { let activeGlobalEnvironment: Environment | undefined = undefined; if (workspaceMeta?.activeGlobalEnvironmentId) { - activeGlobalEnvironment = await models.environment.getById(workspaceMeta.activeGlobalEnvironmentId) || undefined; + activeGlobalEnvironment = (await models.environment.getById(workspaceMeta.activeGlobalEnvironmentId)) || undefined; } const settings = await models.settings.get(); @@ -165,7 +193,10 @@ export const fetchRequestData = async (requestId: string) => { const clientCertificates = await models.clientCertificate.findByParentId(workspaceId); const caCert = await models.caCertificate.findByParentId(workspaceId); const responseId = generateId('res'); - const responsesDir = pathJoin((process.type === 'renderer' ? window : require('electron')).app.getPath('userData'), 'responses'); + const responsesDir = pathJoin( + (process.type === 'renderer' ? window : require('electron')).app.getPath('userData'), + 'responses', + ); const timelinePath = pathJoin(responsesDir, responseId + '.timeline'); return { @@ -204,17 +235,22 @@ export const tryToExecutePreRequestScript = async ( runtime?: SendActionRuntime, ) => { const requestGroups = ancestors.filter(doc => isRequest(doc) || isRequestGroup(doc)) as RequestGroup[]; - const folderScripts = requestGroups.reverse() + const folderScripts = requestGroups + .reverse() .filter(group => group?.preRequestScript) - .map((group, i) => `const fn${i} = async ()=>{ + .map( + (group, i) => `const fn${i} = async ()=>{ ${group.preRequestScript} } await fn${i}(); - `); - const originalRequestGroups = requestGroups - .filter(group => isRequestGroup(group)); - const parentFolders = originalRequestGroups - .map(group => ({ id: group._id, name: group.name, environment: group.environment })); + `, + ); + const originalRequestGroups = requestGroups.filter(group => isRequestGroup(group)); + const parentFolders = originalRequestGroups.map(group => ({ + id: group._id, + name: group.name, + environment: group.environment, + })); if (folderScripts.length === 0) { return { @@ -268,7 +304,13 @@ export const tryToExecutePreRequestScript = async ( parentFolders, }; } - await savePatchesMadeByScript(mutatedContext, environment, baseEnvironment, activeGlobalEnvironment, originalRequestGroups); + await savePatchesMadeByScript( + mutatedContext, + environment, + baseEnvironment, + activeGlobalEnvironment, + originalRequestGroups, + ); return { request: mutatedContext.request, @@ -305,10 +347,9 @@ export async function savePatchesMadeByScript( // persist updated cookieJar if needed if (mutatedContext.cookieJar) { // merge cookies from response to the cookiejar, or cookies from response will not be persisted - await models.cookieJar.update( - mutatedContext.cookieJar, - { cookies: [...(responseCookies || []), ...mutatedContext.cookieJar.cookies] }, - ); + await models.cookieJar.update(mutatedContext.cookieJar, { + cookies: [...(responseCookies || []), ...mutatedContext.cookieJar.cookies], + }); } // when base environment is activated, `mutatedContext.environment` points to it const isActiveEnvironmentBase = mutatedContext.environment?._id === baseEnvironment._id; @@ -316,16 +357,14 @@ export async function savePatchesMadeByScript( const updateEnvironment = async (originEnvironment: Environment, mutatedContextEnvironment: Environment) => { const { environmentType } = originEnvironment; const { data, dataPropertyOrder } = mutatedContextEnvironment; - await models.environment.update( - originEnvironment, - { - data, dataPropertyOrder, - // also update kvPairData when environment type is table view(kv pair) - ...(environmentType === EnvironmentType.KVPAIR && { - kvPairData: getKVPairFromData(data, dataPropertyOrder), - }), - }, - ); + await models.environment.update(originEnvironment, { + data, + dataPropertyOrder, + // also update kvPairData when environment type is table view(kv pair) + ...(environmentType === EnvironmentType.KVPAIR && { + kvPairData: getKVPairFromData(data, dataPropertyOrder), + }), + }); }; if (hasEnvironmentAndIsNotBase) { @@ -387,7 +426,10 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe let vault = undefined; if (globals && vaultEnvironmentPath in globals.data && settings.enableVaultInScripts) { // decrypt and set vault in insomnia sdk if necessary - globals.data[vaultEnvironmentPath] = await maskOrDecryptVaultDataIfNecessary(globals.data[vaultEnvironmentPath], 'script'); + globals.data[vaultEnvironmentPath] = await maskOrDecryptVaultDataIfNecessary( + globals.data[vaultEnvironmentPath], + 'script', + ); vault = globals.data[vaultEnvironmentPath]; } @@ -422,10 +464,12 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe response, vault, globals: globals?.data || undefined, - iterationData: userUploadEnvironment ? { - name: userUploadEnvironment.name, - data: userUploadEnvironment.data || {}, - } : undefined, + iterationData: userUploadEnvironment + ? { + name: userUploadEnvironment.name, + data: userUploadEnvironment.data || {}, + } + : undefined, execution: { ...execution, // keep some existing properties in the after-response script from the pre-request script location: requestLocation, @@ -507,7 +551,7 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe } catch (err) { await fs.promises.appendFile( timelinePath, - serializeNDJSON([{ value: err.message, name: 'Text', timestamp: Date.now() }]) + serializeNDJSON([{ value: err.message, name: 'Text', timestamp: Date.now() }]), ); const requestId = request._id; @@ -563,15 +607,17 @@ type RequestAndContextAndOptionalResponse = RequestContextForScript & { export async function tryToExecuteAfterResponseScript(context: RequestAndContextAndResponse) { const requestGroups = context.ancestors.filter(doc => isRequest(doc) || isRequestGroup(doc)) as RequestGroup[]; - const folderScripts = requestGroups.reverse() + const folderScripts = requestGroups + .reverse() .filter(group => group?.afterResponseScript) - .map((group, i) => `const fn${i} = async ()=>{ + .map( + (group, i) => `const fn${i} = async ()=>{ ${group.afterResponseScript} } await fn${i}(); - `); - const originalRequestGroups = requestGroups - .filter(group => isRequestGroup(group)); + `, + ); + const originalRequestGroups = requestGroups.filter(group => isRequestGroup(group)); if (folderScripts.length === 0) { return { @@ -592,9 +638,22 @@ export async function tryToExecuteAfterResponseScript(context: RequestAndContext const respondedWithoutError = context.response && !('error' in context.response); if (respondedWithoutError) { const resp = context.response as sendCurlAndWriteTimelineResponse; - await savePatchesMadeByScript(postMutatedContext, context.environment, context.baseEnvironment, context.globals, originalRequestGroups, resp.cookies); + await savePatchesMadeByScript( + postMutatedContext, + context.environment, + context.baseEnvironment, + context.globals, + originalRequestGroups, + resp.cookies, + ); } else { - await savePatchesMadeByScript(postMutatedContext, context.environment, context.baseEnvironment, context.globals, originalRequestGroups); + await savePatchesMadeByScript( + postMutatedContext, + context.environment, + context.baseEnvironment, + context.globals, + originalRequestGroups, + ); } return postMutatedContext; @@ -618,8 +677,7 @@ export const tryToInterpolateRequest = async ({ userUploadEnvironment?: UserUploadEnvironment; transientVariables?: Environment; ignoreUndefinedEnvVariable?: boolean; -} -) => { +}) => { try { return await getRenderedRequestAndContext({ request: request, @@ -686,10 +744,19 @@ export async function sendCurlAndWriteTimeline( const timeline: ResponseTimelineEntry[] = []; const authentication = renderedRequest.authentication as RequestAuthentication; - const { finalUrl, socketPath } = transformUrl(renderedRequest.url, renderedRequest.parameters, authentication, renderedRequest.settingEncodeUrl); + const { finalUrl, socketPath } = transformUrl( + renderedRequest.url, + renderedRequest.parameters, + authentication, + renderedRequest.settingEncodeUrl, + ); timeline.push({ value: `Preparing request to ${finalUrl}`, name: 'Text', timestamp: Date.now() }); timeline.push({ value: `Current time is ${new Date().toISOString()}`, name: 'Text', timestamp: Date.now() }); - timeline.push({ value: `${renderedRequest.settingEncodeUrl ? 'Enable' : 'Disable'} automatic URL encoding`, name: 'Text', timestamp: Date.now() }); + timeline.push({ + value: `${renderedRequest.settingEncodeUrl ? 'Enable' : 'Disable'} automatic URL encoding`, + name: 'Text', + timestamp: Date.now(), + }); if (!renderedRequest.settingSendCookies) { timeline.push({ value: 'Disable cookie sending due to user setting', name: 'Text', timestamp: Date.now() }); @@ -707,9 +774,10 @@ export async function sendCurlAndWriteTimeline( }; // NOTE: conditionally use ipc bridge, renderer cannot import native modules directly - const nodejsCurlRequest = process.type === 'renderer' - ? cancellableCurlRequest - : (await import('../main/network/libcurl-promise')).curlRequest; + const nodejsCurlRequest = + process.type === 'renderer' + ? cancellableCurlRequest + : (await import('../main/network/libcurl-promise')).curlRequest; const output = await nodejsCurlRequest(requestOptions); if ('error' in output) { @@ -733,8 +801,15 @@ export async function sendCurlAndWriteTimeline( // todo: move to main process debugTimeline.forEach(entry => timeline.push(entry)); // transform output - const { cookies, rejectedCookies, totalSetCookies } = await extractCookies(headerResults, renderedRequest.cookieJar, finalUrl, renderedRequest.settingStoreCookies); - rejectedCookies.forEach(errorMessage => timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() })); + const { cookies, rejectedCookies, totalSetCookies } = await extractCookies( + headerResults, + renderedRequest.cookieJar, + finalUrl, + renderedRequest.settingStoreCookies, + ); + rejectedCookies.forEach(errorMessage => + timeline.push({ value: `Rejected cookie: ${errorMessage}`, name: 'Text', timestamp: Date.now() }), + ); if (totalSetCookies) { await models.cookieJar.update(renderedRequest.cookieJar, { cookies }); timeline.push({ value: `Saved ${totalSetCookies} cookies`, name: 'Text', timestamp: Date.now() }); @@ -761,7 +836,12 @@ export async function sendCurlAndWriteTimeline( }; } -export const responseTransform = async (patch: ResponsePatch, environmentId: string | null, renderedRequest: RenderedRequest, context: Record) => { +export const responseTransform = async ( + patch: ResponsePatch, + environmentId: string | null, + renderedRequest: RenderedRequest, + context: Record, +) => { const response: ResponsePatch = { ...patch, // important for filter by responses @@ -776,29 +856,42 @@ export const responseTransform = async (patch: ResponsePatch, environmentId: str console.log(`[network] Response failed req=${patch.parentId} err=${response.error || 'n/a'}`); return response; } - console.log(`[network] Response succeeded req=${patch.parentId} status=${response.statusCode || '?'}`,); - return await _applyResponsePluginHooks( - response, - renderedRequest, - context, - ); + console.log(`[network] Response succeeded req=${patch.parentId} status=${response.statusCode || '?'}`); + return await _applyResponsePluginHooks(response, renderedRequest, context); }; -export const transformUrl = (url: string, params: RequestParameter[], authentication: RequestAuthentication, shouldEncode: boolean) => { +export const transformUrl = ( + url: string, + params: RequestParameter[], + authentication: RequestAuthentication, + shouldEncode: boolean, +) => { const authQueryParam = getAuthQueryParams(authentication); - const customUrl = joinUrlAndQueryString(url, buildQueryStringFromParams(authQueryParam ? params.concat([authQueryParam]) : params, true, { strictNullHandling: true })); + const customUrl = joinUrlAndQueryString( + url, + buildQueryStringFromParams(authQueryParam ? params.concat([authQueryParam]) : params, true, { + strictNullHandling: true, + }), + ); const isUnixSocket = customUrl.match(/https?:\/\/unix:\//); if (!isUnixSocket) { return { finalUrl: smartEncodeUrl(customUrl, shouldEncode, { strictNullHandling: true }) }; } // URL prep will convert "unix:/path" hostname to "unix/path" - const match = smartEncodeUrl(customUrl, shouldEncode, { strictNullHandling: true }).match(/(https?:)\/\/unix:?(\/[^:]+):\/(.+)/); + const match = smartEncodeUrl(customUrl, shouldEncode, { strictNullHandling: true }).match( + /(https?:)\/\/unix:?(\/[^:]+):\/(.+)/, + ); const protocol = (match && match[1]) || ''; const socketPath = (match && match[2]) || ''; const socketUrl = (match && match[3]) || ''; return { finalUrl: `${protocol}//${socketUrl}`, socketPath }; }; -const extractCookies = async (headerResults: HeaderResult[], cookieJar: any, finalUrl: string, settingStoreCookies: boolean) => { +const extractCookies = async ( + headerResults: HeaderResult[], + cookieJar: any, + finalUrl: string, + settingStoreCookies: boolean, +) => { // add set-cookie headers to file(cookiejar) and database if (settingStoreCookies) { // supports many set-cookies over many redirects @@ -807,7 +900,11 @@ const extractCookies = async (headerResults: HeaderResult[], cookieJar: any, fin const totalSetCookies = setCookieStrings.length; if (totalSetCookies) { const currentUrl = getCurrentUrl({ headerResults, finalUrl }); - const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ setCookieStrings, currentUrl, cookieJar }); + const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ + setCookieStrings, + currentUrl, + cookieJar, + }); const hasCookiesToPersist = totalSetCookies > rejectedCookies.length; if (hasCookiesToPersist) { return { cookies, rejectedCookies, totalSetCookies }; @@ -835,10 +932,7 @@ export const getCurrentUrl = ({ headerResults, finalUrl }: { headerResults: any; } }; -async function _applyRequestPluginHooks( - renderedRequest: RenderedRequest, - renderedContext: Record, -) { +async function _applyRequestPluginHooks(renderedRequest: RenderedRequest, renderedContext: Record) { const newRenderedRequest = clone(renderedRequest); for (const { plugin, hook } of await plugins.getRequestHooks()) { @@ -899,5 +993,4 @@ async function _applyResponsePluginHooks( settingStoreCookies: renderedRequest.settingStoreCookies, }; } - } diff --git a/packages/insomnia/src/network/o-auth-1/get-token.ts b/packages/insomnia/src/network/o-auth-1/get-token.ts index eea2b92c8c..d40549d981 100644 --- a/packages/insomnia/src/network/o-auth-1/get-token.ts +++ b/packages/insomnia/src/network/o-auth-1/get-token.ts @@ -5,7 +5,7 @@ import crypto from 'crypto'; import OAuth1 from 'oauth-1.0a'; -import type { AUTH_OAUTH_1} from '../../common/constants'; +import type { AUTH_OAUTH_1 } from '../../common/constants'; import { CONTENT_TYPE_FORM_URLENCODED } from '../../common/constants'; import type { RequestAuthentication, RequestBody } from '../../models/request'; import type { OAuth1SignatureMethod } from './constants'; diff --git a/packages/insomnia/src/network/o-auth-2/constants.ts b/packages/insomnia/src/network/o-auth-2/constants.ts index e7a91fda63..0ac4a02616 100644 --- a/packages/insomnia/src/network/o-auth-2/constants.ts +++ b/packages/insomnia/src/network/o-auth-2/constants.ts @@ -4,31 +4,31 @@ export const GRANT_TYPE_PASSWORD = 'password'; export const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials'; export const GRANT_TYPE_REFRESH = 'refresh_token'; export type AuthKeys = - 'access_token' | - 'id_token' | - 'client_id' | - 'client_secret' | - 'audience' | - 'resource' | - 'code_challenge' | - 'code_challenge_method' | - 'code_verifier' | - 'code' | - 'nonce' | - 'error' | - 'error_description' | - 'error_uri' | - 'expires_in' | - 'grant_type' | - 'password' | - 'redirect_uri' | - 'refresh_token' | - 'response_type' | - 'scope' | - 'state' | - 'token_type' | - 'username' | - 'xError' | - 'xResponseId'; + | 'access_token' + | 'id_token' + | 'client_id' + | 'client_secret' + | 'audience' + | 'resource' + | 'code_challenge' + | 'code_challenge_method' + | 'code_verifier' + | 'code' + | 'nonce' + | 'error' + | 'error_description' + | 'error_uri' + | 'expires_in' + | 'grant_type' + | 'password' + | 'redirect_uri' + | 'refresh_token' + | 'response_type' + | 'scope' + | 'state' + | 'token_type' + | 'username' + | 'xError' + | 'xResponseId'; export const PKCE_CHALLENGE_S256 = 'S256'; export const PKCE_CHALLENGE_PLAIN = 'plain'; diff --git a/packages/insomnia/src/network/o-auth-2/get-token.ts b/packages/insomnia/src/network/o-auth-2/get-token.ts index c2f56b542f..dede1dfa95 100644 --- a/packages/insomnia/src/network/o-auth-2/get-token.ts +++ b/packages/insomnia/src/network/o-auth-2/get-token.ts @@ -13,12 +13,15 @@ import type { Response } from '../../models/response'; import { invariant } from '../../utils/invariant'; import { setDefaultProtocol } from '../../utils/url/protocol'; import { getBasicAuthHeader } from '../basic-auth/get-header'; -import { fetchRequestData, fetchRequestGroupData, responseTransform, sendCurlAndWriteTimeline, tryToInterpolateRequest, tryToTransformRequestWithPlugins } from '../network'; import { - type AuthKeys, - GRANT_TYPE_AUTHORIZATION_CODE, - PKCE_CHALLENGE_S256, -} from './constants'; + fetchRequestData, + fetchRequestGroupData, + responseTransform, + sendCurlAndWriteTimeline, + tryToInterpolateRequest, + tryToTransformRequestWithPlugins, +} from '../network'; +import { type AuthKeys, GRANT_TYPE_AUTHORIZATION_CODE, PKCE_CHALLENGE_S256 } from './constants'; const LOCALSTORAGE_KEY_SESSION_ID = 'insomnia::current-oauth-session-id'; @@ -48,7 +51,9 @@ export const getOAuth2Token = async ( if (oAuth2Token) { return oAuth2Token; } - const validGrantType = ['implicit', 'authorization_code', 'password', 'client_credentials'].includes(authentication.grantType); + const validGrantType = ['implicit', 'authorization_code', 'password', 'client_credentials'].includes( + authentication.grantType, + ); invariant(validGrantType, `Invalid grant type ${authentication.grantType}`); if (authentication.grantType === 'implicit') { invariant(authentication.authorizationUrl, 'Missing authorization URL'); @@ -62,9 +67,14 @@ export const getOAuth2Token = async ( ...insertAuthKeyIf('scope', authentication.scope), ...insertAuthKeyIf('state', authentication.state), ...insertAuthKeyIf('audience', authentication.audience), - ...(hasNonce ? [{ - name: 'nonce', value: Math.floor(Math.random() * 9999999999999) + 1 + '', - }] : []), + ...(hasNonce + ? [ + { + name: 'nonce', + value: Math.floor(Math.random() * 9999999999999) + 1 + '', + }, + ] + : []), ].forEach(p => p.value && implicitUrl.searchParams.append(p.name, p.value)); const redirectedTo = await window.main.authorizeUserInWindow({ url: implicitUrl.toString(), @@ -84,10 +94,13 @@ export const getOAuth2Token = async ( invariant(hash, 'No hash found in response URL from OAuth2 provider'); const data = Object.fromEntries(new URLSearchParams(hash)); const old = await models.oAuth2Token.getOrCreateByParentId(requestId); - return models.oAuth2Token.update(old, transformNewAccessTokenToOauthModel({ - ...data, - access_token: data.access_token || data.id_token, - })); + return models.oAuth2Token.update( + old, + transformNewAccessTokenToOauthModel({ + ...data, + access_token: data.access_token || data.id_token, + }), + ); } invariant(authentication.accessTokenUrl, 'Missing access token URL'); let params: RequestHeader[] = []; @@ -96,7 +109,9 @@ export const getOAuth2Token = async ( const codeVerifier = authentication.usePkce ? encodePKCE(crypto.randomBytes(32)) : ''; const usePkceAnd256 = authentication.usePkce && authentication.pkceMethod === PKCE_CHALLENGE_S256; - const codeChallenge = usePkceAnd256 ? encodePKCE(crypto.createHash('sha256').update(codeVerifier).digest()) : codeVerifier; + const codeChallenge = usePkceAnd256 + ? encodePKCE(crypto.createHash('sha256').update(codeVerifier).digest()) + : codeVerifier; const authCodeUrl = new URL(authentication.authorizationUrl); const responseType: OAuth2ResponseType = 'code'; [ @@ -107,17 +122,21 @@ export const getOAuth2Token = async ( ...insertAuthKeyIf('state', authentication.state), ...insertAuthKeyIf('audience', authentication.audience), ...insertAuthKeyIf('resource', authentication.resource), - ...(codeChallenge ? [ - { name: 'code_challenge', value: codeChallenge }, - { name: 'code_challenge_method', value: authentication.pkceMethod }, - ] : []), + ...(codeChallenge + ? [ + { name: 'code_challenge', value: codeChallenge }, + { name: 'code_challenge_method', value: authentication.pkceMethod }, + ] + : []), ].forEach(p => p.value && authCodeUrl.searchParams.append(p.name, p.value)); const redirectedTo = await window.main.authorizeUserInWindow({ url: authCodeUrl.toString(), - urlSuccessRegex: authentication.redirectUrl ? - new RegExp(`${escapeRegex(authentication.redirectUrl)}.*([?&]code=)`, 'i') : /([?&]code=)/i, - urlFailureRegex: authentication.redirectUrl ? - new RegExp(`${escapeRegex(authentication.redirectUrl)}.*([?&]error=)`, 'i') : /([?&]error=)/i, + urlSuccessRegex: authentication.redirectUrl + ? new RegExp(`${escapeRegex(authentication.redirectUrl)}.*([?&]code=)`, 'i') + : /([?&]code=)/i, + urlFailureRegex: authentication.redirectUrl + ? new RegExp(`${escapeRegex(authentication.redirectUrl)}.*([?&]error=)`, 'i') + : /([?&]error=)/i, sessionId: getOAuthSession(), }); console.log('[oauth2] Detected redirect ' + redirectedTo); @@ -166,9 +185,10 @@ export const getOAuth2Token = async ( const response = await sendAccessTokenRequest(requestId, authentication, params, headers); const old = await models.oAuth2Token.getOrCreateByParentId(requestId); - return models.oAuth2Token.update(old, transformNewAccessTokenToOauthModel( - await oauthResponseToAccessToken(authentication.accessTokenUrl, response) - )); + return models.oAuth2Token.update( + old, + transformNewAccessTokenToOauthModel(await oauthResponseToAccessToken(authentication.accessTokenUrl, response)), + ); }; // 1. get token from db and return if valid // 2. if expired, and no refresh token return null @@ -244,10 +264,13 @@ async function getExistingAccessTokenAndRefreshIfExpired( return null; } const old = await models.oAuth2Token.getOrCreateByParentId(requestId); - return models.oAuth2Token.update(old, transformNewAccessTokenToOauthModel({ - ...data, - refresh_token: data.refresh_token || token.refreshToken, - })); + return models.oAuth2Token.update( + old, + transformNewAccessTokenToOauthModel({ + ...data, + refresh_token: data.refresh_token || token.refreshToken, + }), + ); } export const oauthResponseToAccessToken = async (accessTokenUrl: string, response: Response) => { @@ -272,7 +295,9 @@ export const oauthResponseToAccessToken = async (accessTokenUrl: string, respons }; }; -const transformNewAccessTokenToOauthModel = (accessToken: Partial>): Partial => { +const transformNewAccessTokenToOauthModel = ( + accessToken: Partial>, +): Partial => { const expiry = accessToken.expires_in ? +accessToken.expires_in : 0; return { // Calculate expiry date @@ -291,21 +316,21 @@ const transformNewAccessTokenToOauthModel = (accessToken: Partial { +const sendAccessTokenRequest = async ( + requestOrGroupId: string, + authentication: AuthTypeOAuth2, + params: RequestParameter[], + headers: RequestHeader[], +) => { invariant(authentication.accessTokenUrl, 'Missing access token URL'); console.log(`[network] Sending with settings req=${requestOrGroupId}`); // @TODO unpack oauth into regular timeline and remove oauth timeine dialog - const initializedData = isRequestGroupId(requestOrGroupId) ? await fetchRequestGroupData(requestOrGroupId) : await fetchRequestData(requestOrGroupId); + const initializedData = isRequestGroupId(requestOrGroupId) + ? await fetchRequestGroupData(requestOrGroupId) + : await fetchRequestData(requestOrGroupId); - const { - environment, - settings, - clientCertificates, - caCert, - activeEnvironmentId, - timelinePath, - responseId, - } = initializedData; + const { environment, settings, clientCertificates, caCert, activeEnvironmentId, timelinePath, responseId } = + initializedData; const defaultUserAgentHeader: RequestHeader = { name: 'User-Agent', value: `insomnia/${version}` }; const defaultHeaders: RequestHeader[] = [ @@ -316,21 +341,22 @@ const sendAccessTokenRequest = async (requestOrGroupId: string, authentication: if (!settings.disableAppVersionUserAgent) { defaultHeaders.push(defaultUserAgentHeader); } - const newRequest: Request = await models.initModel(models.request.type, { - headers: [ - ...defaultHeaders, - ...headers, - ], - url: setDefaultProtocol(authentication.accessTokenUrl), - method: 'POST', - body: { - mimeType: 'application/x-www-form-urlencoded', - params, + const newRequest: Request = await models.initModel( + models.request.type, + { + headers: [...defaultHeaders, ...headers], + url: setDefaultProtocol(authentication.accessTokenUrl), + method: 'POST', + body: { + mimeType: 'application/x-www-form-urlencoded', + params, + }, }, - }, { - _id: requestOrGroupId + '.other', - parentId: requestOrGroupId, - }); + { + _id: requestOrGroupId + '.other', + parentId: requestOrGroupId, + }, + ); const renderResult = await tryToInterpolateRequest({ request: newRequest, environment: environment._id }); const renderedRequest = await tryToTransformRequestWithPlugins(renderResult); @@ -348,25 +374,28 @@ const sendAccessTokenRequest = async (requestOrGroupId: string, authentication: return await models.response.create(responsePatch); }; export const encodePKCE = (buffer: Buffer) => { - return buffer.toString('base64') - // The characters + / = are reserved for PKCE as per the RFC, - // so we replace them with unreserved characters - // Docs: https://tools.ietf.org/html/rfc7636#section-4.2 - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=/g, ''); + return ( + buffer + .toString('base64') + // The characters + / = are reserved for PKCE as per the RFC, + // so we replace them with unreserved characters + // Docs: https://tools.ietf.org/html/rfc7636#section-4.2 + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, '') + ); }; const tryToParse = (body: string): Record | null => { try { return JSON.parse(body); - } catch (err) { } + } catch (err) {} try { // NOTE: parse does not return a JS Object, so // we cannot use hasOwnProperty on it return querystring.parse(body); - } catch (err) { } + } catch (err) {} return null; }; -const insertAuthKeyIf = (name: AuthKeys, value?: string) => value ? [{ name, value }] : []; +const insertAuthKeyIf = (name: AuthKeys, value?: string) => (value ? [{ name, value }] : []); diff --git a/packages/insomnia/src/network/unit-test-feature.ts b/packages/insomnia/src/network/unit-test-feature.ts index 238d811d76..c081f8cba9 100644 --- a/packages/insomnia/src/network/unit-test-feature.ts +++ b/packages/insomnia/src/network/unit-test-feature.ts @@ -1,13 +1,20 @@ import { stats } from '../models'; import { getBodyBuffer } from '../models/response'; import { parseGraphQLReqeustBody } from '../utils/graph-ql'; -import { fetchRequestData, responseTransform, sendCurlAndWriteTimeline, tryToInterpolateRequest, tryToTransformRequestWithPlugins } from './network'; +import { + fetchRequestData, + responseTransform, + sendCurlAndWriteTimeline, + tryToInterpolateRequest, + tryToTransformRequestWithPlugins, +} from './network'; export function getSendRequestCallback() { return async function sendRequest(requestId: string) { stats.incrementExecutedRequests(); // NOTE: unit tests will use the UI selected environment - const { request, + const { + request, environment, settings, clientCertificates, @@ -28,14 +35,16 @@ export function getSendRequestCallback() { caCert, settings, timelinePath, - responseId + responseId, ); const res = await responseTransform(response, activeEnvironmentId, renderedRequest, renderResult.context); const { statusCode: status, statusMessage, headers: headerArray, elapsedTime: responseTime } = res; - const headers = headerArray?.reduce((acc, { name, value }) => ({ ...acc, [name.toLowerCase() || '']: value || '' }), []); - const bodyBuffer = await getBodyBuffer(res) as Buffer; + const headers = headerArray?.reduce( + (acc, { name, value }) => ({ ...acc, [name.toLowerCase() || '']: value || '' }), + [], + ); + const bodyBuffer = (await getBodyBuffer(res)) as Buffer; const data = bodyBuffer ? bodyBuffer.toString('utf8') : undefined; return { status, statusMessage, data, headers, responseTime }; - }; } diff --git a/packages/insomnia/src/plugins/context/__tests__/app.test.ts b/packages/insomnia/src/plugins/context/__tests__/app.test.ts index 77fe1fb2f4..a5dd583152 100644 --- a/packages/insomnia/src/plugins/context/__tests__/app.test.ts +++ b/packages/insomnia/src/plugins/context/__tests__/app.test.ts @@ -4,35 +4,22 @@ import appPackageJson from '../../../../package.json'; import * as plugin from '../app'; describe('init()', () => { - it('initializes correctly', async () => { const result = plugin.init(); expect(Object.keys(result)).toEqual(['app', '__private']); - expect(Object.keys(result.app).sort()).toEqual([ - 'alert', - 'clipboard', - 'dialog', - 'getPath', - 'getInfo', - 'prompt', - 'showSaveDialog', - ].sort()); - expect(Object.keys(result.app.clipboard).sort()).toEqual([ - 'clear', - 'readText', - 'writeText', - ].sort()); + expect(Object.keys(result.app).sort()).toEqual( + ['alert', 'clipboard', 'dialog', 'getPath', 'getInfo', 'prompt', 'showSaveDialog'].sort(), + ); + expect(Object.keys(result.app.clipboard).sort()).toEqual(['clear', 'readText', 'writeText'].sort()); }); }); describe('app.getInfo()', () => { - it('provides app info', async () => { const result = plugin.init(); expect(result.app.getInfo()).toEqual({ - 'version': appPackageJson.version, - 'platform': process.platform, + version: appPackageJson.version, + platform: process.platform, }); }); - }); diff --git a/packages/insomnia/src/plugins/context/__tests__/request.test.ts b/packages/insomnia/src/plugins/context/__tests__/request.test.ts index a07f2b4810..4251d26f4c 100644 --- a/packages/insomnia/src/plugins/context/__tests__/request.test.ts +++ b/packages/insomnia/src/plugins/context/__tests__/request.test.ts @@ -17,7 +17,7 @@ const CONTEXT = { describe('init()', () => { beforeEach(async () => { - await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); + await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); await models.workspace.create({ _id: 'wrk_1', @@ -98,7 +98,7 @@ describe('init()', () => { describe('request.*', () => { beforeEach(async () => { - await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); + await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); await models.workspace.create({ _id: 'wrk_1', @@ -138,16 +138,14 @@ describe('request.*', () => { }); it('works for basic getters', async () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { }); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const result = plugin.init(await models.request.getById('req_1'), CONTEXT); expect(result.request.getId()).toBe('req_1'); expect(result.request.getName()).toBe('My Request'); expect(result.request.getUrl()).toBe(''); expect(result.request.getMethod()).toBe('GET'); expect(result.request.getBodyText()).toBe('body'); - expect(consoleWarnSpy).toHaveBeenCalledWith( - 'request.getBodyText() is deprecated. Use request.getBody() instead.', - ); + expect(consoleWarnSpy).toHaveBeenCalledWith('request.getBodyText() is deprecated. Use request.getBody() instead.'); expect(result.request.getAuthentication()).toEqual({ type: 'oauth2', }); diff --git a/packages/insomnia/src/plugins/context/__tests__/store.test.ts b/packages/insomnia/src/plugins/context/__tests__/store.test.ts index 802442d885..cd87282509 100644 --- a/packages/insomnia/src/plugins/context/__tests__/store.test.ts +++ b/packages/insomnia/src/plugins/context/__tests__/store.test.ts @@ -9,24 +9,15 @@ const PLUGIN = { }; describe('init()', () => { - it('initializes correctly', async () => { const result = plugin.init({ name: PLUGIN, }); - expect(Object.keys(result.store).sort()).toEqual([ - 'all', - 'clear', - 'getItem', - 'hasItem', - 'removeItem', - 'setItem', - ]); + expect(Object.keys(result.store).sort()).toEqual(['all', 'clear', 'getItem', 'hasItem', 'removeItem', 'setItem']); }); }); describe('store.*', () => { - it('all methods work', async () => { const p = plugin.init(PLUGIN); // Null item for no result diff --git a/packages/insomnia/src/plugins/context/app.tsx b/packages/insomnia/src/plugins/context/app.tsx index 882af4bbbf..cf275ff744 100644 --- a/packages/insomnia/src/plugins/context/app.tsx +++ b/packages/insomnia/src/plugins/context/app.tsx @@ -11,10 +11,13 @@ import { WrapperModal } from '../../ui/components/modals/wrapper-modal'; import { invariant } from '../../utils/invariant'; export interface PrivateProperties { - loadRendererModules: () => Promise<{ - ReactDOM: typeof ReactDOM; - React: typeof React; - } | {}>; + loadRendererModules: () => Promise< + | { + ReactDOM: typeof ReactDOM; + React: typeof React; + } + | {} + >; } export const init = (renderPurpose: RenderPurpose = 'general'): { app: AppContext; __private: PrivateProperties } => ({ @@ -50,7 +53,7 @@ export const init = (renderPurpose: RenderPurpose = 'general'): { app: AppContex selected = value; }, // don't resolve the overall promise until the modal has hidden after clicking submit - onHide: () => selected !== null ? resolve(selected) : reject(new Error(`Prompt ${title} cancelled`)), + onHide: () => (selected !== null ? resolve(selected) : reject(new Error(`Prompt ${title} cancelled`))), }); }); }, diff --git a/packages/insomnia/src/plugins/context/data.ts b/packages/insomnia/src/plugins/context/data.ts index 80a53a39f2..9b833c02d8 100644 --- a/packages/insomnia/src/plugins/context/data.ts +++ b/packages/insomnia/src/plugins/context/data.ts @@ -15,11 +15,10 @@ const getWorkspaces = (activeProjectId?: string) => { if (activeProjectId) { return models.workspace.findByParentId(activeProjectId); } - // This code path was kept in case there was ever a time when the app wouldn't have an active project. - // In over 5 months of monitoring in production, we never saw this happen. - // Keeping it for defensive purposes, but it's not clear if it's necessary. - return models.workspace.all(); - + // This code path was kept in case there was ever a time when the app wouldn't have an active project. + // In over 5 months of monitoring in production, we never saw this happen. + // Keeping it for defensive purposes, but it's not clear if it's necessary. + return models.workspace.all(); }; // Only in the case of running unit tests from Inso can activeProjectId be undefined. This is because the concept of a project doesn't exist in git/insomnia sync or an export file @@ -53,11 +52,12 @@ export const init = (activeProjectId?: string) => ({ }, }, export: { - insomnia: async ({ - workspace, - }: { workspace: Workspace }) => { + insomnia: async ({ workspace }: { workspace: Workspace }) => { if (workspace) { - const insomniaExport = await getInsomniaV5DataExport({ workspaceId: workspace._id, includePrivateEnvironments: false }); + const insomniaExport = await getInsomniaV5DataExport({ + workspaceId: workspace._id, + includePrivateEnvironments: false, + }); return [insomniaExport]; } @@ -67,20 +67,18 @@ export const init = (activeProjectId?: string) => ({ const allInsomniaExports = []; for (const workspace of workspaces) { - const insomniaExport = await getInsomniaV5DataExport({ workspaceId: workspace._id, includePrivateEnvironments: false }); + const insomniaExport = await getInsomniaV5DataExport({ + workspaceId: workspace._id, + includePrivateEnvironments: false, + }); allInsomniaExports.push(insomniaExport); } return allInsomniaExports; }, - har: async ({ - workspace, - includePrivate, - }: HarExport = {}) => exportWorkspacesHAR( - workspace ? [workspace] : await getWorkspaces(activeProjectId), - Boolean(includePrivate), - ), + har: async ({ workspace, includePrivate }: HarExport = {}) => + exportWorkspacesHAR(workspace ? [workspace] : await getWorkspaces(activeProjectId), Boolean(includePrivate)), }, }, }); diff --git a/packages/insomnia/src/plugins/context/network.ts b/packages/insomnia/src/plugins/context/network.ts index 3127d2a271..e171898621 100644 --- a/packages/insomnia/src/plugins/context/network.ts +++ b/packages/insomnia/src/plugins/context/network.ts @@ -1,12 +1,19 @@ import * as models from '../../models'; import type { Request } from '../../models/request'; -import { fetchRequestData, responseTransform, sendCurlAndWriteTimeline, tryToInterpolateRequest, tryToTransformRequestWithPlugins } from '../../network/network'; +import { + fetchRequestData, + responseTransform, + sendCurlAndWriteTimeline, + tryToInterpolateRequest, + tryToTransformRequestWithPlugins, +} from '../../network/network'; export function init() { return { network: { async sendRequest(req: Request, extraInfo?: { requestChain: string[] }) { - const { request, + const { + request, environment, settings, clientCertificates, @@ -16,7 +23,12 @@ export function init() { responseId, } = await fetchRequestData(req._id); - const renderResult = await tryToInterpolateRequest({ request, environment: environment._id, purpose: 'send', extraInfo }); + const renderResult = await tryToInterpolateRequest({ + request, + environment: environment._id, + purpose: 'send', + extraInfo, + }); const renderedRequest = await tryToTransformRequestWithPlugins(renderResult); const response = await sendCurlAndWriteTimeline( renderedRequest, @@ -24,9 +36,14 @@ export function init() { caCert, settings, timelinePath, - responseId + responseId, + ); + const responsePatch = await responseTransform( + response, + activeEnvironmentId, + renderedRequest, + renderResult.context, ); - const responsePatch = await responseTransform(response, activeEnvironmentId, renderedRequest, renderResult.context); return models.response.create(responsePatch, settings.maxHistoryResponses); }, }, diff --git a/packages/insomnia/src/plugins/context/request.ts b/packages/insomnia/src/plugins/context/request.ts index 07e21651a9..ad1ec33da4 100644 --- a/packages/insomnia/src/plugins/context/request.ts +++ b/packages/insomnia/src/plugins/context/request.ts @@ -1,21 +1,14 @@ import * as misc from '../../common/misc'; import type { RequestBody } from '../../models/request'; import type { RenderedRequest } from '../../templating/types'; -export function filterParameters( - parameters: T[], - name: string, -): T[] { +export function filterParameters(parameters: T[], name: string): T[] { if (!Array.isArray(parameters) || !name) { return []; } return parameters.filter(h => (!h || !h.name ? false : h.name === name)); } -export function init( - renderedRequest: RenderedRequest | null, - renderedContext: Record, - readOnly = false, -) { +export function init(renderedRequest: RenderedRequest | null, renderedContext: Record, readOnly = false) { if (!renderedRequest) { throw new Error('contexts.request initialized without request'); } @@ -58,9 +51,7 @@ export function init( } }, - getEnvironmentVariable( - name: string, - ): string | number | boolean | Record | any[] | null { + getEnvironmentVariable(name: string): string | number | boolean | Record | any[] | null { return renderedContext[name]; }, @@ -96,8 +87,7 @@ export function init( const header = headers[headers.length - 1]; return header.value || ''; } - return null; - + return null; }, getHeaders() { @@ -145,8 +135,7 @@ export function init( const parameter = parameters[parameters.length - 1]; return parameter.value || ''; } - return null; - + return null; }, getParameters() { diff --git a/packages/insomnia/src/plugins/context/response.ts b/packages/insomnia/src/plugins/context/response.ts index e3863226df..e9e85837ba 100644 --- a/packages/insomnia/src/plugins/context/response.ts +++ b/packages/insomnia/src/plugins/context/response.ts @@ -74,8 +74,7 @@ export function init(response?: MaybeResponse) { } else if (matchedHeaders.length === 1) { return matchedHeaders[0].value; } - return null; - + return null; }, getHeaders() { diff --git a/packages/insomnia/src/plugins/context/store.ts b/packages/insomnia/src/plugins/context/store.ts index c2f1f568b4..d22e1a4ea9 100644 --- a/packages/insomnia/src/plugins/context/store.ts +++ b/packages/insomnia/src/plugins/context/store.ts @@ -15,7 +15,7 @@ export interface PluginStore { >; } -export function init(plugin: Plugin):{store: PluginStore} { +export function init(plugin: Plugin): { store: PluginStore } { return { store: { async hasItem(key: string) { @@ -45,8 +45,8 @@ export function init(plugin: Plugin):{store: PluginStore} { key: string; value: string; }[] - > { - const docs = await models.pluginData.all(plugin.name) || []; + > { + const docs = (await models.pluginData.all(plugin.name)) || []; return docs.map(d => ({ value: d.value, key: d.key, diff --git a/packages/insomnia/src/plugins/create.ts b/packages/insomnia/src/plugins/create.ts index fea61d754b..00055991e4 100644 --- a/packages/insomnia/src/plugins/create.ts +++ b/packages/insomnia/src/plugins/create.ts @@ -2,12 +2,12 @@ import electron from 'electron'; import fs from 'fs'; import path from 'path'; -export async function createPlugin( - moduleName: string, - version: string, - mainJs: string, -) { - const pluginDir = path.join(process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : electron).app.getPath('userData'), 'plugins', moduleName); +export async function createPlugin(moduleName: string, version: string, mainJs: string) { + const pluginDir = path.join( + process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : electron).app.getPath('userData'), + 'plugins', + moduleName, + ); if (fs.existsSync(pluginDir)) { throw new Error(`Plugin already exists at "${pluginDir}"`); diff --git a/packages/insomnia/src/plugins/index.ts b/packages/insomnia/src/plugins/index.ts index 2ccd39c858..d47ce7962b 100644 --- a/packages/insomnia/src/plugins/index.ts +++ b/packages/insomnia/src/plugins/index.ts @@ -104,11 +104,7 @@ export async function init() { await reloadPlugins(); } -async function _traversePluginPath( - pluginMap: Record, - allPaths: string[], - allConfigs: PluginConfigMap, -) { +async function _traversePluginPath(pluginMap: Record, allPaths: string[], allConfigs: PluginConfigMap) { for (const p of allPaths) { if (!fs.existsSync(p)) { continue; @@ -157,15 +153,16 @@ async function _traversePluginPath( description: pluginJson.description || pluginJson.insomnia.description || '', version: pluginJson.version || 'unknown', directory: modulePath || '', - config: (pluginJson.name in allConfigs) - ? allConfigs[pluginJson.name] - : { disabled: false }, + config: pluginJson.name in allConfigs ? allConfigs[pluginJson.name] : { disabled: false }, module: module, }; } catch (err) { showError({ title: 'Plugin Error', - message: 'Failed to load plugin ' + filename + '. Please contact the plugin author sharing the below stack trace to help them to ensure compatibility with the latest Insomnia.', + message: + 'Failed to load plugin ' + + filename + + '. Please contact the plugin author sharing the below stack trace to help them to ensure compatibility with the latest Insomnia.', error: err, }); } @@ -189,10 +186,12 @@ export async function getPlugins(force = false): Promise { return path.join(process.env['HOME'] || '/', p.slice(1)); } return p; - }); // Make sure the default directories exist - const pluginPath = path.join(process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : electron).app.getPath('userData'), 'plugins'); + const pluginPath = path.join( + process.env['INSOMNIA_DATA_PATH'] || (process.type === 'renderer' ? window : electron).app.getPath('userData'), + 'plugins', + ); fs.mkdirSync(pluginPath, { recursive: true }); // Also look in node_modules folder in each directory const basePaths = [pluginPath, ...extraPaths]; @@ -306,38 +305,40 @@ export async function getTemplateTags(): Promise { } export async function getRequestHooks(): Promise { - let functions: RequestHook[] = [{ - plugin: { - name: 'default-headers', - description: 'Set default headers for all requests', - version: '0.0.0', - directory: '', - config: { - disabled: false, + let functions: RequestHook[] = [ + { + plugin: { + name: 'default-headers', + description: 'Set default headers for all requests', + version: '0.0.0', + directory: '', + config: { + disabled: false, + }, + module: {}, }, - module: {}, - }, - hook: context => { - const headers = context.request.getEnvironmentVariable('DEFAULT_HEADERS'); - if (!headers) { - return; - } - for (const name of Object.keys(headers)) { - const value = headers[name]; - if (context.request.hasHeader(name)) { - console.log(`[header] Skip setting default header ${name}. Already set to ${value}`); - continue; + hook: context => { + const headers = context.request.getEnvironmentVariable('DEFAULT_HEADERS'); + if (!headers) { + return; } - if (value === 'null') { - context.request.removeHeader(name); - console.log(`[header] Remove default header ${name}`); - } else { - context.request.setHeader(name, value); - console.log(`[header] Set default header ${name}: ${value}`); + for (const name of Object.keys(headers)) { + const value = headers[name]; + if (context.request.hasHeader(name)) { + console.log(`[header] Skip setting default header ${name}. Already set to ${value}`); + continue; + } + if (value === 'null') { + context.request.removeHeader(name); + console.log(`[header] Remove default header ${name}`); + } else { + context.request.setHeader(name, value); + console.log(`[header] Set default header ${name}: ${value}`); + } } - } + }, }, - }]; + ]; for (const plugin of await getActivePlugins()) { const moreFunctions = plugin.module.requestHooks || []; diff --git a/packages/insomnia/src/plugins/misc.test.ts b/packages/insomnia/src/plugins/misc.test.ts index 3a456f6e06..da239b2fbb 100644 --- a/packages/insomnia/src/plugins/misc.test.ts +++ b/packages/insomnia/src/plugins/misc.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; -import type { PluginTheme} from './misc'; +import type { PluginTheme } from './misc'; import { containsNunjucks, validateTheme, validateThemeName } from './misc'; describe('containsNunjucks', () => { @@ -21,9 +21,10 @@ describe('validateTheme', () => { const nunjucksValue = '{{ nunjucks.4.lyfe }}'; const name = 'mock-plugin'; const displayName = 'Mock Plugin'; - const mockMessage = (path: string[]) => `[plugin] Nunjucks values in plugin themes are no longer valid. The plugin ${displayName} (${name}) has an invalid value, "${nunjucksValue}" at the path $.theme.${path.join('.')}`; + const mockMessage = (path: string[]) => + `[plugin] Nunjucks values in plugin themes are no longer valid. The plugin ${displayName} (${name}) has an invalid value, "${nunjucksValue}" at the path $.theme.${path.join('.')}`; - vi.spyOn(console, 'error').mockImplementation(() => { }); + vi.spyOn(console, 'error').mockImplementation(() => {}); it('will validate rawCSS in the plugin theme', () => { const pluginTheme: PluginTheme = { diff --git a/packages/insomnia/src/plugins/misc.ts b/packages/insomnia/src/plugins/misc.ts index b7cd0be6be..96f4f0405b 100644 --- a/packages/insomnia/src/plugins/misc.ts +++ b/packages/insomnia/src/plugins/misc.ts @@ -92,9 +92,7 @@ export const validateThemeName = (name: string) => { return validName; }; -export const containsNunjucks = (data: string) => ( - data.includes('{{') && data.includes('}}') -); +export const containsNunjucks = (data: string) => data.includes('{{') && data.includes('}}'); const getChildValue = (theme: any, path: string[]) => { return path.reduce((acc, v: string) => { try { @@ -116,7 +114,9 @@ export const validateTheme = (pluginTheme: PluginTheme) => { } if (typeof data === 'string' && containsNunjucks(data)) { - console.error(`[plugin] Nunjucks values in plugin themes are no longer valid. The plugin ${pluginTheme.displayName} (${pluginTheme.name}) has an invalid value, "${data}" at the path $.theme.${keyPath.join('.')}`); + console.error( + `[plugin] Nunjucks values in plugin themes are no longer valid. The plugin ${pluginTheme.displayName} (${pluginTheme.name}) has an invalid value, "${data}" at the path $.theme.${keyPath.join('.')}`, + ); } if (typeof data === 'object') { @@ -130,18 +130,13 @@ export const validateTheme = (pluginTheme: PluginTheme) => { check(['rawCss']); - [ - 'background', - 'foreground', - 'highlight', - ].forEach(rootPath => { + ['background', 'foreground', 'highlight'].forEach(rootPath => { check([rootPath]); Object.keys(pluginTheme.theme.styles ?? {}).forEach(style => { check(['styles', style, rootPath]); }); }); - }; export const generateThemeCSS = (pluginTheme: PluginTheme) => { @@ -165,19 +160,11 @@ export const generateThemeCSS = (pluginTheme: PluginTheme) => { if (theme.styles) { const styles = theme.styles; // Dropdown Menus - css += wrapStyles( - name, - '.theme--dropdown__menu', - getThemeBlockCSS(styles.dropdown || styles.dialog), - ); + css += wrapStyles(name, '.theme--dropdown__menu', getThemeBlockCSS(styles.dropdown || styles.dialog)); // Tooltips css += wrapStyles(name, '.theme--tooltip', getThemeBlockCSS(styles.tooltip || styles.dialog)); // Overlay - css += wrapStyles( - name, - '.theme--transparent-overlay', - getThemeBlockCSS(styles.transparentOverlay), - ); + css += wrapStyles(name, '.theme--transparent-overlay', getThemeBlockCSS(styles.transparentOverlay)); // Dialogs css += wrapStyles(name, '.theme--dialog', getThemeBlockCSS(styles.dialog)); css += wrapStyles(name, '.theme--dialog__header', getThemeBlockCSS(styles.dialogHeader)); diff --git a/packages/insomnia/src/plugins/themes.ts b/packages/insomnia/src/plugins/themes.ts index 0d52e39770..516fdb3326 100644 --- a/packages/insomnia/src/plugins/themes.ts +++ b/packages/insomnia/src/plugins/themes.ts @@ -1,4 +1,3 @@ - import r from './themes/colorblind-dark'; import a from './themes/default'; import k from './themes/gruvbox'; diff --git a/packages/insomnia/src/plugins/themes/colorblind-dark.ts b/packages/insomnia/src/plugins/themes/colorblind-dark.ts index 38676213d7..2de36ddf5a 100644 --- a/packages/insomnia/src/plugins/themes/colorblind-dark.ts +++ b/packages/insomnia/src/plugins/themes/colorblind-dark.ts @@ -3,51 +3,51 @@ export default { displayName: 'Dark Colorblind', theme: { background: { - default: '#21262D', // primary background color - success: '#0080FF', // POST request, 200 OK, parameter names - notice: '#E8F086', // SEND button, GET request - warning: '#A691AE', // PUT request - danger: '#CC5500', // DELETE request - surprise: '#FFC20A', // accent (Dashboard link, branch button, add plugin button) - info: '#58A6FF', // OPTIONS AND HEAD request + default: '#21262D', // primary background color + success: '#0080FF', // POST request, 200 OK, parameter names + notice: '#E8F086', // SEND button, GET request + warning: '#A691AE', // PUT request + danger: '#CC5500', // DELETE request + surprise: '#FFC20A', // accent (Dashboard link, branch button, add plugin button) + info: '#58A6FF', // OPTIONS AND HEAD request }, foreground: { - default: '#fff', // primary font color - success: '#000', // secondary font color for success background - notice: '#000', // secondary font color for notice background - warning: '#fff', // secondary font color for warning background - danger: '#fff', // secondary font color for danger background - surprise: '#000', // secondary font color for surprise background - info: '#000', // secondary font color for info background + default: '#fff', // primary font color + success: '#000', // secondary font color for success background + notice: '#000', // secondary font color for notice background + warning: '#fff', // secondary font color for warning background + danger: '#fff', // secondary font color for danger background + surprise: '#000', // secondary font color for surprise background + info: '#000', // secondary font color for info background }, highlight: { - default: '#D3D3D3', // sidebar highlight color + default: '#D3D3D3', // sidebar highlight color }, styles: { appHeader: { foreground: { - surprise: '#000', // branch button font color + surprise: '#000', // branch button font color }, }, paneHeader: { foreground: { - surprise: '#000', // accent font color - info: '#000', // response font color + surprise: '#000', // accent font color + info: '#000', // response font color }, }, editor: { foreground: { - default: '#000', // primary editor font color - surprise: '#000', // accent font color - info: '#000', // response font color + default: '#000', // primary editor font color + surprise: '#000', // accent font color + info: '#000', // response font color }, }, dialog: { background: { - default: '#2E4052', // modal primary background color + default: '#2E4052', // modal primary background color }, foreground: { - default: '#fff', // primary font color for modals + default: '#fff', // primary font color for modals }, }, }, diff --git a/packages/insomnia/src/preload.ts b/packages/insomnia/src/preload.ts index 3b0d6ade4d..ec10632f3f 100644 --- a/packages/insomnia/src/preload.ts +++ b/packages/insomnia/src/preload.ts @@ -129,29 +129,32 @@ const main: Window['main'] = { }, }, hiddenBrowserWindow: { - runScript: options => new Promise(async (resolve, reject) => { - const isPortAlive = ports.get('hiddenWindowPort') !== undefined; - await ipcRenderer.invoke('open-channel-to-hidden-browser-window', isPortAlive); + runScript: options => + new Promise(async (resolve, reject) => { + const isPortAlive = ports.get('hiddenWindowPort') !== undefined; + await ipcRenderer.invoke('open-channel-to-hidden-browser-window', isPortAlive); - const port = ports.get('hiddenWindowPort'); - invariant(port, 'hiddenWindowPort is undefined'); + const port = ports.get('hiddenWindowPort'); + invariant(port, 'hiddenWindowPort is undefined'); - port.onmessage = event => { - console.log('[preload] received result:', event.data); - if (event.data.error) { - reject(new Error(event.data.error)); - } - resolve(event.data); - }; + port.onmessage = event => { + console.log('[preload] received result:', event.data); + if (event.data.error) { + reject(new Error(event.data.error)); + } + resolve(event.data); + }; - port.postMessage({ ...options, type: 'runPreRequestScript' }); - }), + port.postMessage({ ...options, type: 'runPreRequestScript' }); + }), }, - landingPageRendered: (landingPage, tags) => ipcRenderer.send('landingPageRendered', { - landingPage, - tags, - }), - extractJsonFileFromPostmanDataDumpArchive: archivePath => ipcRenderer.invoke('extractJsonFileFromPostmanDataDumpArchive', archivePath), + landingPageRendered: (landingPage, tags) => + ipcRenderer.send('landingPageRendered', { + landingPage, + tags, + }), + extractJsonFileFromPostmanDataDumpArchive: archivePath => + ipcRenderer.invoke('extractJsonFileFromPostmanDataDumpArchive', archivePath), }; ipcRenderer.on('hidden-browser-window-response-listener', event => { diff --git a/packages/insomnia/src/requireInterceptor.ts b/packages/insomnia/src/requireInterceptor.ts index 13a0e83632..cef3d30552 100644 --- a/packages/insomnia/src/requireInterceptor.ts +++ b/packages/insomnia/src/requireInterceptor.ts @@ -44,12 +44,7 @@ export const requireInterceptor = (moduleName: string): any => { ].includes(moduleName) ) { return require(moduleName); - } else if ( - [ - 'atob', - 'btoa', - ].includes(moduleName) - ) { + } else if (['atob', 'btoa'].includes(moduleName)) { return moduleName === 'atob' ? atob : btoa; } else if ( [ diff --git a/packages/insomnia/src/scriptExecutor.ts b/packages/insomnia/src/scriptExecutor.ts index 1be2c55576..ba0ba0daea 100644 --- a/packages/insomnia/src/scriptExecutor.ts +++ b/packages/insomnia/src/scriptExecutor.ts @@ -3,13 +3,24 @@ import { appendFile } from 'node:fs/promises'; import * as _ from 'lodash'; import { initInsomniaObject, InsomniaObject } from '../../insomnia-scripting-environment/src/objects'; -import { getNewConsole, mergeClientCertificates, mergeCookieJar, mergeRequests, mergeSettings, type RequestContext } from '../../insomnia-scripting-environment/src/objects'; +import { + getNewConsole, + mergeClientCertificates, + mergeCookieJar, + mergeRequests, + mergeSettings, + type RequestContext, +} from '../../insomnia-scripting-environment/src/objects'; import { invariant } from '../src/utils/invariant'; import { requireInterceptor } from './requireInterceptor'; -export const runScript = async ( - { script, context }: { script: string; context: RequestContext }, -): Promise => { +export const runScript = async ({ + script, + context, +}: { + script: string; + context: RequestContext; +}): Promise => { // console.log(script); const scriptConsole = getNewConsole(); @@ -21,7 +32,7 @@ export const runScript = async ( return result; }; - const AsyncFunction = (async () => { }).constructor; + const AsyncFunction = (async () => {}).constructor; const executeScript = AsyncFunction( 'insomnia', 'require', @@ -36,7 +47,7 @@ export const runScript = async ( ` const $ = insomnia; ${script}; - return insomnia;` + return insomnia;`, ); const mutatedInsomniaObject = await executeScript( @@ -91,21 +102,15 @@ export const runScript = async ( }; // proxiedSetTimeout has to be here as callback could be an async task -function proxiedSetTimeout( - callback: () => void, - ms?: number | undefined, -) { +function proxiedSetTimeout(callback: () => void, ms?: number | undefined) { let resolveHdl: (value: unknown) => void; new Promise(resolve => { resolveHdl = resolve; }); - return setTimeout( - () => { - callback(); - resolveHdl(null); - }, - ms, - ); + return setTimeout(() => { + callback(); + resolveHdl(null); + }, ms); } diff --git a/packages/insomnia/src/sync/delta/__tests__/diff.test.ts b/packages/insomnia/src/sync/delta/__tests__/diff.test.ts index 9c431823a9..75710f2ee5 100644 --- a/packages/insomnia/src/sync/delta/__tests__/diff.test.ts +++ b/packages/insomnia/src/sync/delta/__tests__/diff.test.ts @@ -7,7 +7,7 @@ describe('diff()', () => { const result = __internal.getBlockMap('Hello World!!', 3); expect(result).toEqual({ - dbc2d1fed0dc37a70aea0f376958c802eddc0559: [ + 'dbc2d1fed0dc37a70aea0f376958c802eddc0559': [ { start: 0, len: 3, @@ -28,7 +28,7 @@ describe('diff()', () => { hash: '3603dd999f7dd952041d3bdb27f74e511cfd6b2a', }, ], - c20d168802bbae1c84f90b9b0495e0d918da3aea: [ + 'c20d168802bbae1c84f90b9b0495e0d918da3aea': [ { start: 9, len: 3, diff --git a/packages/insomnia/src/sync/delta/__tests__/patch.test.ts b/packages/insomnia/src/sync/delta/__tests__/patch.test.ts index 695116121d..77e68c1e7b 100644 --- a/packages/insomnia/src/sync/delta/__tests__/patch.test.ts +++ b/packages/insomnia/src/sync/delta/__tests__/patch.test.ts @@ -6,10 +6,7 @@ import { patch } from '../patch'; describe('patch()', () => { it('works on many examples', () => { const things = [ - [ - 'Hello, this is a pretty long sentence about not much at all.', - 'Hello, this is a pretty short sentence.', - ], + ['Hello, this is a pretty long sentence about not much at all.', 'Hello, this is a pretty short sentence.'], ['xxxxxxxxxxyyyyyyyyyyyyyyyyzzzzzzzzzzzzzzzz', 'abc'], ['xyz', 'xyz'], ]; diff --git a/packages/insomnia/src/sync/delta/diff.ts b/packages/insomnia/src/sync/delta/diff.ts index e6bc250838..e2c883d3b3 100644 --- a/packages/insomnia/src/sync/delta/diff.ts +++ b/packages/insomnia/src/sync/delta/diff.ts @@ -25,7 +25,7 @@ export function diff(source: string, target: string, blockSize: number): Operati // Iterate over source blocks in order and match them to target let lastTargetMatch = 0; - for (let targetPosition = 0; targetPosition < target.length;) { + for (let targetPosition = 0; targetPosition < target.length; ) { const targetBlock = getBlock(target, targetPosition, blockSize); const sourceBlocks = sourceBlockMap[targetBlock.hash] || []; @@ -103,7 +103,7 @@ function getBlock(value: string, start: number, blockSize: number): Block { function getBlockMap(value: string, blockSize: number): Record { const map: Record = {}; - for (let i = 0; i < value.length;) { + for (let i = 0; i < value.length; ) { const block = getBlock(value, i, blockSize); if (map[block.hash]) { diff --git a/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts b/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts index d49b4c795e..c79bab9351 100644 --- a/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts +++ b/packages/insomnia/src/sync/git/__tests__/git-vcs.test.ts @@ -37,66 +37,71 @@ describe('Git-VCS', () => { // foo.txt and bar.txt should be in the unstaged list const status = await GitVCS.status(); expect(status.staged).toEqual([]); - expect(status.unstaged).toEqual([{ - 'name': '', - 'path': '.insomnia/bar.txt', - 'status': [0, 2, 0], - }, - { - 'name': '', - 'path': '.insomnia/foo.txt', - 'status': [0, 2, 0], + expect(status.unstaged).toEqual([ + { + name: '', + path: '.insomnia/bar.txt', + status: [0, 2, 0], + }, + { + name: '', + path: '.insomnia/foo.txt', + status: [0, 2, 0], }, ]); const fooStatus = status.unstaged.find(f => f.path.includes(fooTxt)); - fooStatus && await GitVCS.stageChanges([fooStatus]); + fooStatus && (await GitVCS.stageChanges([fooStatus])); const status2 = await GitVCS.status(); - expect(status2.staged).toEqual([{ - 'name': '', - 'path': '.insomnia/foo.txt', - 'status': [0, 2, 2], - }]); + expect(status2.staged).toEqual([ + { + name: '', + path: '.insomnia/foo.txt', + status: [0, 2, 2], + }, + ]); expect(status2.unstaged).toEqual([ { - 'name': '', - 'path': '.insomnia/bar.txt', - 'status': [0, 2, 0], + name: '', + path: '.insomnia/bar.txt', + status: [0, 2, 0], }, ]); const barStatus = status2.unstaged.find(f => f.path.includes(barTxt)); - barStatus && await GitVCS.stageChanges([barStatus]); + barStatus && (await GitVCS.stageChanges([barStatus])); const status3 = await GitVCS.status(); expect(status3.staged).toEqual([ { - 'name': '', - 'path': '.insomnia/bar.txt', - 'status': [0, 2, 2], + name: '', + path: '.insomnia/bar.txt', + status: [0, 2, 2], }, { - 'name': '', - 'path': '.insomnia/foo.txt', - 'status': [0, 2, 2], + name: '', + path: '.insomnia/foo.txt', + status: [0, 2, 2], }, ]); const fooStatus3 = status3.staged.find(f => f.path.includes(fooTxt)); - fooStatus3 && await GitVCS.unstageChanges([fooStatus3]); + fooStatus3 && (await GitVCS.unstageChanges([fooStatus3])); const status4 = await GitVCS.status(); expect(status4).toEqual({ - staged: [{ - 'name': '', - 'path': '.insomnia/bar.txt', - 'status': [0, 2, 2], - }], + staged: [ + { + name: '', + path: '.insomnia/bar.txt', + status: [0, 2, 2], + }, + ], unstaged: [ { - 'name': '', - 'path': '.insomnia/foo.txt', - 'status': [0, 2, 0], + name: '', + path: '.insomnia/foo.txt', + status: [0, 2, 0], }, ], }); @@ -135,24 +140,22 @@ describe('Git-VCS', () => { const status = await GitVCS.status(); const fooStatus = status.unstaged.find(f => f.path.includes(fooTxt)); - fooStatus && await GitVCS.stageChanges([fooStatus]); + fooStatus && (await GitVCS.stageChanges([fooStatus])); const status2 = await GitVCS.status(); - expect(status2.staged).toEqual([{ - 'name': '', - 'path': '.insomnia/foo.txt', - 'status': [0, 2, 2], - }]); + expect(status2.staged).toEqual([ + { + name: '', + path: '.insomnia/foo.txt', + status: [0, 2, 2], + }, + ]); expect(status2.unstaged).toEqual([ { - 'name': '', - 'path': '.insomnia/bar.txt', - 'status': [ - 0, - 2, - 0, - ], + name: '', + path: '.insomnia/bar.txt', + status: [0, 2, 0], }, ]); @@ -163,13 +166,9 @@ describe('Git-VCS', () => { expect(status3.staged).toEqual([]); expect(status3.unstaged).toEqual([ { - 'name': '', - 'path': '.insomnia/bar.txt', - 'status': [ - 0, - 2, - 0, - ], + name: '', + path: '.insomnia/bar.txt', + status: [0, 2, 0], }, ]); @@ -220,14 +219,14 @@ First commit! await GitVCS.setAuthor({ name: 'Karen Brown', email: 'karen@example.com' }); const status = await GitVCS.status(); const fooStatus = status.unstaged.find(f => f.path.includes(fooTxt)); - fooStatus && await GitVCS.stageChanges([fooStatus]); + fooStatus && (await GitVCS.stageChanges([fooStatus])); await GitVCS.commit('First commit!'); expect((await GitVCS.log()).length).toBe(1); await GitVCS.checkout('new-branch'); expect((await GitVCS.log()).length).toBe(1); const status2 = await GitVCS.status(); const barStatus = status2.unstaged.find(f => f.path.includes(barTxt)); - barStatus && await GitVCS.stageChanges([barStatus]); + barStatus && (await GitVCS.stageChanges([barStatus])); await GitVCS.commit('Second commit!'); expect((await GitVCS.log()).length).toBe(2); await GitVCS.checkout('main'); @@ -285,12 +284,12 @@ First commit! const status2 = await GitVCS.status(); expect(status2).toEqual({ - 'staged': [], - 'unstaged': [ + staged: [], + unstaged: [ { - 'name': '', - 'path': '.insomnia/folder/bar.txt', - 'status': [1, 2, 1], + name: '', + path: '.insomnia/folder/bar.txt', + status: [1, 2, 1], }, ], }); @@ -341,12 +340,12 @@ First commit! await GitVCS.discardChanges(changesToUndo); const status3 = await GitVCS.status(); expect(status3).toEqual({ - 'staged': [], - 'unstaged': [ + staged: [], + unstaged: [ { - 'name': '', - 'path': '.insomnia/foo3.txt', - 'status': [1, 2, 1], + name: '', + path: '.insomnia/foo3.txt', + status: [1, 2, 1], }, ], }); diff --git a/packages/insomnia/src/sync/git/__tests__/ne-db-client.test.ts b/packages/insomnia/src/sync/git/__tests__/ne-db-client.test.ts index f0464659a2..e3d534e048 100644 --- a/packages/insomnia/src/sync/git/__tests__/ne-db-client.test.ts +++ b/packages/insomnia/src/sync/git/__tests__/ne-db-client.test.ts @@ -19,7 +19,7 @@ describe('NeDBClient', () => { beforeEach(async () => { workspaceBuilder.reset(); setupDateMocks(); - await db.init(models.types(), { inMemoryOnly: true }, true, () => { },); + await db.init(models.types(), { inMemoryOnly: true }, true, () => {}); // Create some sample models await models.project.create({ _id: 'proj_1', @@ -194,9 +194,7 @@ describe('NeDBClient', () => { // Act const promiseResult = neDbClient.writeFile(filePath, YAML.stringify(env)); // Assert - await expect(promiseResult).rejects.toThrowError( - 'Doc _id does not match file path [env_1 != env_2]', - ); + await expect(promiseResult).rejects.toThrowError('Doc _id does not match file path [env_1 != env_2]'); }); it('should throw error if type does not match', async () => { @@ -212,9 +210,7 @@ describe('NeDBClient', () => { // Act const promiseResult = neDbClient.writeFile(filePath, YAML.stringify(env)); // Assert - await expect(promiseResult).rejects.toThrowError( - 'Doc type does not match file path [Environment != Request]', - ); + await expect(promiseResult).rejects.toThrowError('Doc type does not match file path [Environment != Request]'); }); }); diff --git a/packages/insomnia/src/sync/git/__tests__/util.ts b/packages/insomnia/src/sync/git/__tests__/util.ts index 4934006302..65aee637de 100644 --- a/packages/insomnia/src/sync/git/__tests__/util.ts +++ b/packages/insomnia/src/sync/git/__tests__/util.ts @@ -8,8 +8,7 @@ export function setupDateMocks() { if (!arg) { return new Date(ts++); } - super(arg); - + super(arg); } getTimezoneOffset() { diff --git a/packages/insomnia/src/sync/git/fs-client.ts b/packages/insomnia/src/sync/git/fs-client.ts index 5bfdc4a2fd..94402a5a16 100644 --- a/packages/insomnia/src/sync/git/fs-client.ts +++ b/packages/insomnia/src/sync/git/fs-client.ts @@ -18,19 +18,23 @@ export const fsClient = (basePath: string) => { console.log(`[fsClient] Created in ${basePath}`); fs.mkdirSync(basePath, { recursive: true }); - const wrap = (fn: FSWraps) => async (filePath: string, ...args: any[]) => { - const modifiedPath = path.join(basePath, path.normalize(filePath)); + const wrap = + (fn: FSWraps) => + async (filePath: string, ...args: any[]) => { + const modifiedPath = path.join(basePath, path.normalize(filePath)); - // @ts-expect-error -- TSCONVERSION - return fn(modifiedPath, ...args); - }; + // @ts-expect-error -- TSCONVERSION + return fn(modifiedPath, ...args); + }; - const wrapSymlink = (fn: typeof fs.promises.symlink) => async (filePath: string, target: string, ...args: any[]) => { - const modifiedPath = path.join(basePath, path.normalize(filePath)); - const modifiedTarget = path.join(basePath, path.normalize(target)); + const wrapSymlink = + (fn: typeof fs.promises.symlink) => + async (filePath: string, target: string, ...args: any[]) => { + const modifiedPath = path.join(basePath, path.normalize(filePath)); + const modifiedTarget = path.join(basePath, path.normalize(target)); - return fn(modifiedPath, modifiedTarget, ...args); - }; + return fn(modifiedPath, modifiedTarget, ...args); + }; return { promises: { diff --git a/packages/insomnia/src/sync/git/git-vcs.ts b/packages/insomnia/src/sync/git/git-vcs.ts index 08dac04a99..054423d19f 100644 --- a/packages/insomnia/src/sync/git/git-vcs.ts +++ b/packages/insomnia/src/sync/git/git-vcs.ts @@ -35,9 +35,7 @@ interface GitCredentialsOAuth { export type GitCredentials = GitCredentialsBase | GitCredentialsOAuth; -export const isGitCredentialsOAuth = ( - credentials: GitCredentials -): credentials is GitCredentialsOAuth => { +export const isGitCredentialsOAuth = (credentials: GitCredentials): credentials is GitCredentialsOAuth => { return 'oauth2format' in credentials; }; @@ -181,14 +179,7 @@ export class GitVCS { } } - async initFromClone({ - repoId, - url, - gitCredentials, - directory, - fs, - gitDirectory, - }: InitFromCloneOptions) { + async initFromClone({ repoId, url, gitCredentials, directory, fs, gitDirectory }: InitFromCloneOptions) { this._baseOpts = { ...this._baseOpts, ...gitCallbacks(gitCredentials), @@ -241,9 +232,7 @@ export class GitVCS { branches.push(branch); } - console.log( - `[git] Local branches: ${branches.join(', ')} (current: ${branch})` - ); + console.log(`[git] Local branches: ${branches.join(', ')} (current: ${branch})`); return GitVCS.sortBranches(branches); } @@ -267,11 +256,7 @@ export class GitVCS { }); console.log({ branches }); // Don't care about returning remote HEAD - return GitVCS.sortBranches( - branches - .filter(b => b.ref !== 'HEAD') - .map(b => b.ref.replace('refs/heads/', '')) - ); + return GitVCS.sortBranches(branches.filter(b => b.ref !== 'HEAD').map(b => b.ref.replace('refs/heads/', ''))); } catch (e) { console.log(`[git] Failed to list remote branches for ${uri}`, e); return []; @@ -321,11 +306,7 @@ export class GitVCS { const headOid = headType === 'blob' ? await head?.oid() : undefined; const stageOid = stageType === 'blob' ? await stage?.oid() : undefined; let workdirOid; - if ( - headType !== 'blob' && - workdirType === 'blob' && - stageType !== 'blob' - ) { + if (headType !== 'blob' && workdirType === 'blob' && stageType !== 'blob') { workdirOid = '42'; } else if (workdirType === 'blob') { workdirOid = await workdir?.oid(); @@ -423,7 +404,8 @@ export class GitVCS { ], map: async function map(filepath, [head, workdir, stage]) { if (baseOpts.legacyDiff) { - const isInsomniaFile = filepath.startsWith(GIT_INSOMNIA_DIR_NAME) || filepath.startsWith('insomnia.') || filepath === '.'; + const isInsomniaFile = + filepath.startsWith(GIT_INSOMNIA_DIR_NAME) || filepath.startsWith('insomnia.') || filepath === '.'; if (!isInsomniaFile) { return null; } @@ -434,10 +416,12 @@ export class GitVCS { } } - if (await git.isIgnored({ - ...baseOpts, - filepath, - })) { + if ( + await git.isIgnored({ + ...baseOpts, + filepath, + }) + ) { return null; } const [headType, workdirType, stageType] = await Promise.all([ @@ -471,11 +455,7 @@ export class GitVCS { const headOid = headType === 'blob' ? await head?.oid() : undefined; const stageOid = stageType === 'blob' ? await stage?.oid() : undefined; let workdirOid; - if ( - headType !== 'blob' && - workdirType === 'blob' && - stageType !== 'blob' - ) { + if (headType !== 'blob' && workdirType === 'blob' && stageType !== 'blob') { // We don't actually NEED the sha. Any sha will do // TODO: update this logic to handle N trees instead of just 3. workdirOid = '42'; @@ -667,7 +647,7 @@ export class GitVCS { if (response.error) { console.log('[git] Push rejected', response); throw new Error( - `Push rejected with errors: ${response.error}.\n\nGo to View > Toggle DevTools > Console for more information.` + `Push rejected with errors: ${response.error}.\n\nGo to View > Toggle DevTools > Console for more information.`, ); } @@ -675,7 +655,7 @@ export class GitVCS { console.log('[git] Push failed with errors', response.errors); const errorsString = JSON.stringify(response.errors); throw new Error( - `Push rejected with errors: ${errorsString}.\n\nGo to View > Toggle DevTools > Console for more information.` + `Push rejected with errors: ${errorsString}.\n\nGo to View > Toggle DevTools > Console for more information.`, ); } @@ -699,27 +679,22 @@ export class GitVCS { throw new Error('Cannot pull with uncommitted changes, please commit local changes first.'); } console.log('[git] Pull remote=origin', await this.getCurrentBranch()); - return git.pull({ - ...this._baseOpts, - ...gitCallbacks(gitCredentials), - remote: 'origin', - singleBranch: true, - }).catch( - async err => { + return git + .pull({ + ...this._baseOpts, + ...gitCallbacks(gitCredentials), + remote: 'origin', + singleBranch: true, + }) + .catch(async err => { if (err instanceof git.Errors.MergeConflictError) { const oursBranch = await this.getCurrentBranch(); const theirsBranch = `origin/${oursBranch}`; - return await this._collectMergeConflicts( - err, - oursBranch, - theirsBranch, - ); + return await this._collectMergeConflicts(err, oursBranch, theirsBranch); } - throw err; - - }, - ); + throw err; + }); } // Collect merge conflict details from isomorphic-git git.Errors.MergeConflictError and throw a MergeConflictError which will be used to display the conflicts in the SyncMergeModal @@ -728,9 +703,7 @@ export class GitVCS { oursBranch: string, theirsBranch: string, ) { - const { - filepaths, bothModified, deleteByUs, deleteByTheirs, - } = mergeConflictError.data; + const { filepaths, bothModified, deleteByUs, deleteByTheirs } = mergeConflictError.data; if (filepaths.length) { const mergeConflicts: MergeConflict[] = []; const conflictPathsObj = { @@ -738,11 +711,7 @@ export class GitVCS { deleteByUs, deleteByTheirs, }; - const conflictTypeList: (keyof typeof conflictPathsObj)[] = [ - 'bothModified', - 'deleteByUs', - 'deleteByTheirs', - ]; + const conflictTypeList: (keyof typeof conflictPathsObj)[] = ['bothModified', 'deleteByUs', 'deleteByTheirs']; const oursHeadCommitOid = await git.resolveRef({ ...this._baseOpts, @@ -757,16 +726,16 @@ export class GitVCS { const _baseOpts = this._baseOpts; function readBlob(filepath: string, oid: string) { - return git.readBlob({ - ..._baseOpts, - oid, - filepath, - }).then( - ({ blob, oid: blobId }) => ({ + return git + .readBlob({ + ..._baseOpts, + oid, + filepath, + }) + .then(({ blob, oid: blobId }) => ({ blobContent: parse(Buffer.from(blob).toString('utf8')), blobId, - }) - ); + })); } function readOursBlob(filepath: string) { @@ -780,9 +749,9 @@ export class GitVCS { for (const conflictType of conflictTypeList) { const conflictPaths = conflictPathsObj[conflictType]; const message = { - 'bothModified': 'both modified', - 'deleteByUs': 'you deleted and they modified', - 'deleteByTheirs': 'they deleted and you modified', + bothModified: 'both modified', + deleteByUs: 'you deleted and they modified', + deleteByTheirs: 'they deleted and you modified', }[conflictType]; for (const conflictPath of conflictPaths) { let mineBlobContent = null; @@ -792,19 +761,13 @@ export class GitVCS { let theirsBlobId = null; if (conflictType !== 'deleteByUs') { - const { - blobContent, - blobId, - } = await readOursBlob(conflictPath); + const { blobContent, blobId } = await readOursBlob(conflictPath); mineBlobContent = blobContent; mineBlobId = blobId; } if (conflictType !== 'deleteByTheirs') { - const { - blobContent, - blobId, - } = await readTheirsBlob(conflictPath); + const { blobContent, blobId } = await readTheirsBlob(conflictPath); theirsBlobContent = blobContent; theirsBlobId = blobId; } @@ -832,7 +795,6 @@ export class GitVCS { commitMessage: `Merge branch '${theirsBranch}' into ${oursBranch}`, commitParent: [oursHeadCommitOid, theirsHeadCommitOid], }); - } else { throw new Error('Merge conflict filepaths is of length 0'); } @@ -858,15 +820,10 @@ export class GitVCS { for (const conflict of handledMergeConflicts) { assertIsPromiseFsClient(this._baseOpts.fs); if (conflict.theirsBlobContent) { - await this._baseOpts.fs.promises.writeFile( - conflict.key, - stringify(conflict.theirsBlobContent), - ); + await this._baseOpts.fs.promises.writeFile(conflict.key, stringify(conflict.theirsBlobContent)); await git.add({ ...this._baseOpts, filepath: conflict.key }); } else { - await this._baseOpts.fs.promises.unlink( - conflict.key, - ); + await this._baseOpts.fs.promises.unlink(conflict.key); await git.remove({ ...this._baseOpts, filepath: conflict.key }); } } @@ -896,19 +853,16 @@ export class GitVCS { } const oursBranch = await this.getCurrentBranch(); console.log(`[git] Merge ${oursBranch} <-- ${theirsBranch}`); - return git.merge({ - ...this._baseOpts, - ours: oursBranch, - theirs: theirsBranch, - abortOnConflict: false, - }).catch( - async err => { + return git + .merge({ + ...this._baseOpts, + ours: oursBranch, + theirs: theirsBranch, + abortOnConflict: false, + }) + .catch(async err => { if (err instanceof git.Errors.MergeConflictError) { - return await this._collectMergeConflicts( - err, - oursBranch, - theirsBranch, - ); + return await this._collectMergeConflicts(err, oursBranch, theirsBranch); } if (err instanceof git.Errors.MergeNotSupportedError) { @@ -918,8 +872,7 @@ export class GitVCS { } throw err; - }, - ); + }); } async fetch({ @@ -943,7 +896,6 @@ export class GitVCS { depth, prune: true, pruneTags: true, - }); } @@ -1072,7 +1024,6 @@ export class GitVCS { filepaths: [convertToPosixSep(change.path)], }); } - } } @@ -1084,22 +1035,24 @@ export class GitVCS { } else if (b === 'master') { return 1; } - return b > a ? -1 : 1; - + return b > a ? -1 : 1; }); return newBranches; } } export class MergeConflictError extends Error { - constructor(msg: string, data: { - conflicts: MergeConflict[]; - labels: { - ours: string; - theirs: string; - }; - commitMessage: string; - commitParent: string[]; - }) { + constructor( + msg: string, + data: { + conflicts: MergeConflict[]; + labels: { + ours: string; + theirs: string; + }; + commitMessage: string; + commitParent: string[]; + }, + ) { super(msg); this.data = data; } diff --git a/packages/insomnia/src/sync/git/mem-client.ts b/packages/insomnia/src/sync/git/mem-client.ts index c8c9c89196..b94e346541 100644 --- a/packages/insomnia/src/sync/git/mem-client.ts +++ b/packages/insomnia/src/sync/git/mem-client.ts @@ -88,10 +88,7 @@ export class MemClient { console.log(await next(baseDir, '')); } - async readFile( - filePath: string, - options: BufferEncoding | { encoding?: BufferEncoding } = {}, - ) { + async readFile(filePath: string, options: BufferEncoding | { encoding?: BufferEncoding } = {}) { filePath = path.normalize(filePath); if (typeof options === 'string') { @@ -109,8 +106,7 @@ export class MemClient { if (encoding) { return raw.toString(encoding); } - return raw; - + return raw; } async writeFile( diff --git a/packages/insomnia/src/sync/git/ne-db-client.ts b/packages/insomnia/src/sync/git/ne-db-client.ts index 638c4f69e9..8e0ee7c1e7 100644 --- a/packages/insomnia/src/sync/git/ne-db-client.ts +++ b/packages/insomnia/src/sync/git/ne-db-client.ts @@ -28,7 +28,6 @@ export class NeDBClient { this._workspaceId = workspaceId; this._projectId = projectId; - } static createClient(workspaceId: string, projectId: string): PromiseFsClient { @@ -37,10 +36,7 @@ export class NeDBClient { }; } - async readFile( - filePath: string, - options?: BufferEncoding | { encoding?: BufferEncoding }, - ) { + async readFile(filePath: string, options?: BufferEncoding | { encoding?: BufferEncoding }) { filePath = path.normalize(filePath); options = options || {}; @@ -80,8 +76,7 @@ export class NeDBClient { if (options.encoding) { return raw.toString(options.encoding); } - return raw; - + return raw; } async writeFile(filePath: string, data: Buffer | string) { @@ -111,7 +106,10 @@ export class NeDBClient { } if (isWorkspace(doc)) { - console.log('[git] setting workspace parent to be that of the active project', { original: doc.parentId, new: this._projectId }); + console.log('[git] setting workspace parent to be that of the active project', { + original: doc.parentId, + new: this._projectId, + }); // Whenever we write a workspace into nedb we should set the parentId to be that of the current project // This is because the parentId (or a project) is not synced into git, so it will be cleared whenever git writes the workspace into the db, thereby removing it from the project on the client // In order to reproduce this bug, comment out the following line, then clone a repository into a local project, then open the workspace, you'll notice it will have moved into the default project @@ -240,14 +238,13 @@ export class NeDBClient { mtimeMs: doc.modified, }); } - return new Stat({ - type: 'dir', - mode: 0o777, - size: 0, - ino: 0, - mtimeMs: 0, - }); - + return new Stat({ + type: 'dir', + mode: 0o777, + size: 0, + ino: 0, + mtimeMs: 0, + }); } async readlink(filePath: string, ...x: any[]) { diff --git a/packages/insomnia/src/sync/git/project-ne-db-client.ts b/packages/insomnia/src/sync/git/project-ne-db-client.ts index af6861a210..5b4e855a37 100644 --- a/packages/insomnia/src/sync/git/project-ne-db-client.ts +++ b/packages/insomnia/src/sync/git/project-ne-db-client.ts @@ -29,10 +29,7 @@ export class GitProjectNeDBClient { }; } - async readFile( - filePath: string, - options?: BufferEncoding | { encoding?: BufferEncoding }, - ) { + async readFile(filePath: string, options?: BufferEncoding | { encoding?: BufferEncoding }) { if (!filePath.endsWith('.yaml')) { throw this._errMissing(filePath); } @@ -59,8 +56,7 @@ export class GitProjectNeDBClient { if (options.encoding) { return raw.toString(options.encoding); } - return raw; - + return raw; } catch (err) { throw this._errMissing(filePath); } @@ -91,13 +87,16 @@ export class GitProjectNeDBClient { const workspace = dataToImport.find(isWorkspace) as Workspace | undefined; - const isExistingWorkspace = workspace && await models.workspace.getById(workspace._id); + const isExistingWorkspace = workspace && (await models.workspace.getById(workspace._id)); // Remove the workspace if it already exists to clean up any descendants that might have been removed. - isExistingWorkspace && await models.workspace.remove(workspace); + isExistingWorkspace && (await models.workspace.remove(workspace)); for (const doc of dataToImport) { if (isWorkspace(doc)) { - console.log('[git] setting workspace parent to be that of the active project', { original: doc.parentId, new: this._projectId }); + console.log('[git] setting workspace parent to be that of the active project', { + original: doc.parentId, + new: this._projectId, + }); // Whenever we write a workspace into nedb we should set the parentId to be that of the current project // This is because the parentId (or a project) is not synced into git, so it will be cleared whenever git writes the workspace into the db, thereby removing it from the project on the client // In order to reproduce this bug, comment out the following line, then clone a repository into a local project, then open the workspace, you'll notice it will have moved into the default project @@ -139,7 +138,9 @@ export class GitProjectNeDBClient { }, }); - const hasDirectoryInsomniaFiles = workspaceMetas.some(({ gitFilePath }) => gitFilePath && path.dirname(gitFilePath) === filePath); + const hasDirectoryInsomniaFiles = workspaceMetas.some( + ({ gitFilePath }) => gitFilePath && path.dirname(gitFilePath) === filePath, + ); if (hasDirectoryInsomniaFiles) { const workspacePaths = workspaceMetas @@ -190,14 +191,13 @@ export class GitProjectNeDBClient { mtimeMs: doc?.meta?.modified || 0, }); } - return new Stat({ - type: 'dir', - mode: 0o777, - size: 0, - ino: 0, - mtimeMs: 0, - }); - + return new Stat({ + type: 'dir', + mode: 0o777, + size: 0, + ino: 0, + mtimeMs: 0, + }); } async readlink(filePath: string, ...x: any[]) { diff --git a/packages/insomnia/src/sync/git/project-routable-fs-client.ts b/packages/insomnia/src/sync/git/project-routable-fs-client.ts index fdbdeb6083..5322a6796e 100644 --- a/packages/insomnia/src/sync/git/project-routable-fs-client.ts +++ b/packages/insomnia/src/sync/git/project-routable-fs-client.ts @@ -1,7 +1,17 @@ import type * as git from 'isomorphic-git'; import path from 'path'; -type Methods = 'readFile' | 'writeFile' | 'unlink' | 'readdir' | 'mkdir' | 'rmdir' | 'stat' | 'lstat' | 'readlink' | 'symlink'; +type Methods = + | 'readFile' + | 'writeFile' + | 'unlink' + | 'readdir' + | 'mkdir' + | 'rmdir' + | 'stat' + | 'lstat' + | 'readlink' + | 'symlink'; /** * An isometric-git FS client that can route to various client depending on what the filePath is. @@ -21,7 +31,7 @@ export function projectRoutableFSClient( for (const prefix of Object.keys(otherFS)) { if (filePath.indexOf(path.normalize(prefix)) === 0) { // TODO: remove non-null assertion - + return otherFS[prefix].promises[method]!(filePath, ...args); } } @@ -30,7 +40,6 @@ export function projectRoutableFSClient( // console.log('[routablefs] Executing', method, filePath, { args }); // Fallback to default if no prefix matched // TODO: remove non-null assertion - // We store insomnia files in the database and all other files in a folder named 'other' on disk // When we read a directory, we need to merge the two lists to provide the full list of files diff --git a/packages/insomnia/src/sync/git/routable-fs-client.ts b/packages/insomnia/src/sync/git/routable-fs-client.ts index 17e748d82e..5c4eec99ae 100644 --- a/packages/insomnia/src/sync/git/routable-fs-client.ts +++ b/packages/insomnia/src/sync/git/routable-fs-client.ts @@ -1,7 +1,17 @@ import type * as git from 'isomorphic-git'; import path from 'path'; -type Methods = 'readFile' | 'writeFile' | 'unlink' | 'readdir' | 'mkdir' | 'rmdir' | 'stat' | 'lstat' | 'readlink' | 'symlink'; +type Methods = + | 'readFile' + | 'writeFile' + | 'unlink' + | 'readdir' + | 'mkdir' + | 'rmdir' + | 'stat' + | 'lstat' + | 'readlink' + | 'symlink'; /** * An isometric-git FS client that can route to various client depending on what the filePath is. @@ -10,17 +20,14 @@ type Methods = 'readFile' | 'writeFile' | 'unlink' | 'readdir' | 'mkdir' | 'rmdi * @param otherFS – map of path prefixes to clients * @returns {{promises: *}} */ -export function routableFSClient( - defaultFS: git.PromiseFsClient, - otherFS: Record, -) { +export function routableFSClient(defaultFS: git.PromiseFsClient, otherFS: Record) { const execMethod = async (method: Methods, filePath: string, ...args: any[]) => { filePath = path.normalize(filePath); for (const prefix of Object.keys(otherFS)) { if (filePath.indexOf(path.normalize(prefix)) === 0) { // TODO: remove non-null assertion - + return otherFS[prefix].promises[method]!(filePath, ...args); } } @@ -29,7 +36,7 @@ export function routableFSClient( // console.log('[routablefs] Executing', method, filePath, { args }); // Fallback to default if no prefix matched // TODO: remove non-null assertion - + const result = await defaultFS.promises[method]!(filePath, ...args); // If the method is returning a list of files for the root directory // we need to return the actual result plus inject the .insomnia directory diff --git a/packages/insomnia/src/sync/git/system-error.ts b/packages/insomnia/src/sync/git/system-error.ts index 0237563c22..72b85dd127 100644 --- a/packages/insomnia/src/sync/git/system-error.ts +++ b/packages/insomnia/src/sync/git/system-error.ts @@ -24,13 +24,7 @@ export class SystemError extends Error { path: SystemErrorConstructor['path']; syscall: SystemErrorConstructor['syscall']; - constructor({ - code, - errno, - message, - path, - syscall, - }: SystemErrorConstructor) { + constructor({ code, errno, message, path, syscall }: SystemErrorConstructor) { super(message); const error = new Error(message); diff --git a/packages/insomnia/src/sync/git/utils.ts b/packages/insomnia/src/sync/git/utils.ts index 8ea061f4a0..9e19bc6181 100644 --- a/packages/insomnia/src/sync/git/utils.ts +++ b/packages/insomnia/src/sync/git/utils.ts @@ -12,96 +12,94 @@ const onMessage: MessageCallback = message => { const onAuthFailure = (credentials?: GitCredentials): AuthFailureCallback => - async (message, auth) => { - console.log(`[git-event] Auth Failure: ${message}`); + async (message, auth) => { + console.log(`[git-event] Auth Failure: ${message}`); - // Try to refresh the token if auth failed. - // Whenever we return a new GitAuth object from this function - // isomorphic-git will retry the request with the new credentials. - // https://isomorphic-git.org/docs/en/onAuthFailure#docsNav - if ( - credentials && - 'oauth2format' in credentials && - credentials.oauth2format === 'gitlab' - ) { - console.log('[git-event] Attempting to refresh token'); - try { - const providerCredentials = await gitCredentials.getByProvider(credentials.oauth2format); - if (providerCredentials?.refreshToken) { - console.log('[git-event] Token refreshed'); - return { - ...auth, - password: providerCredentials.refreshToken, - headers: { - ...auth.headers, - Authorization: `Bearer ${providerCredentials.refreshToken}`, - }, - }; - } - } catch (error) { - console.warn('[git-event] Failed to refresh token', error); - return; + // Try to refresh the token if auth failed. + // Whenever we return a new GitAuth object from this function + // isomorphic-git will retry the request with the new credentials. + // https://isomorphic-git.org/docs/en/onAuthFailure#docsNav + if (credentials && 'oauth2format' in credentials && credentials.oauth2format === 'gitlab') { + console.log('[git-event] Attempting to refresh token'); + try { + const providerCredentials = await gitCredentials.getByProvider(credentials.oauth2format); + if (providerCredentials?.refreshToken) { + console.log('[git-event] Token refreshed'); + return { + ...auth, + password: providerCredentials.refreshToken, + headers: { + ...auth.headers, + Authorization: `Bearer ${providerCredentials.refreshToken}`, + }, + }; } + } catch (error) { + console.warn('[git-event] Failed to refresh token', error); + return; } + } - return; - }; + return; + }; const onAuthSuccess: AuthSuccessCallback = message => { console.log(`[git-event] Auth Success: ${message}`); }; -const onAuth = (credentials?: GitCredentials): AuthCallback => async (): Promise => { - if (!credentials) { - console.log('[git-event] No credentials'); - return { - username: '', - password: '', - }; - } - - if ('oauth2format' in credentials && credentials.oauth2format) { - console.log('[git-event] Using OAuth2 credentials'); - const providerCredentials = await gitCredentials.getByProvider(credentials.oauth2format); - - if (!providerCredentials) { - console.warn('[git-event] No OAuth2 credentials found'); +const onAuth = + (credentials?: GitCredentials): AuthCallback => + async (): Promise => { + if (!credentials) { + console.log('[git-event] No credentials'); return { username: '', password: '', }; } - // Transform the credentials to the format expected by isomorphic-git https://isomorphic-git.org/docs/en/onAuth#oauth2-tokens - if (providerCredentials.provider === 'gitlab') { - return { - username: 'oauth2', - password: providerCredentials.token, - }; + if ('oauth2format' in credentials && credentials.oauth2format) { + console.log('[git-event] Using OAuth2 credentials'); + const providerCredentials = await gitCredentials.getByProvider(credentials.oauth2format); + + if (!providerCredentials) { + console.warn('[git-event] No OAuth2 credentials found'); + return { + username: '', + password: '', + }; + } + + // Transform the credentials to the format expected by isomorphic-git https://isomorphic-git.org/docs/en/onAuth#oauth2-tokens + if (providerCredentials.provider === 'gitlab') { + return { + username: 'oauth2', + password: providerCredentials.token, + }; + } + + if (providerCredentials.provider === 'github') { + return { + username: providerCredentials.token, + password: 'x-oauth-basic', + }; + } + + if (providerCredentials.provider === 'githubapp') { + return { + username: 'x-access-token', + password: providerCredentials.token, + }; + } } - if (providerCredentials.provider === 'github') { - return { - username: providerCredentials.token, - password: 'x-oauth-basic', - }; - } - - if (providerCredentials.provider === 'githubapp') { - return { - username: 'x-access-token', - password: providerCredentials.token, - }; - } - } - - console.log('[git-event] Using basic auth credentials'); - return { - username: credentials.username, - // @ts-expect-error -- TSCONVERSION this needs to be handled better if credentials is undefined or which union type - password: credentials.password || credentials.token, + console.log('[git-event] Using basic auth credentials'); + return { + username: credentials.username, + // @ts-expect-error -- TSCONVERSION this needs to be handled better if credentials is undefined or which union type + password: credentials.password || credentials.token, + }; }; -}; export const getAuthorFromGitRepository = async (gitRepositoryId: string): Promise => { const gitRepo = await gitRepository.getById(gitRepositoryId); diff --git a/packages/insomnia/src/sync/ignore-keys.ts b/packages/insomnia/src/sync/ignore-keys.ts index 747d767cb9..e85c87740f 100644 --- a/packages/insomnia/src/sync/ignore-keys.ts +++ b/packages/insomnia/src/sync/ignore-keys.ts @@ -35,10 +35,9 @@ export const deleteKeys = (doc: T) => { export const resetKeys = (doc: T) => { if (isWorkspace(doc)) { - (Object.keys(RESET_WORKSPACE_KEYS) as (keyof typeof RESET_WORKSPACE_KEYS)[]) - .forEach(key => { - // @ts-expect-error -- mapping unsoundness - doc[key] = RESET_WORKSPACE_KEYS[key]; - }); + (Object.keys(RESET_WORKSPACE_KEYS) as (keyof typeof RESET_WORKSPACE_KEYS)[]).forEach(key => { + // @ts-expect-error -- mapping unsoundness + doc[key] = RESET_WORKSPACE_KEYS[key]; + }); } }; diff --git a/packages/insomnia/src/sync/store/drivers/file-system-driver.ts b/packages/insomnia/src/sync/store/drivers/file-system-driver.ts index 7b0df5363e..e571916704 100644 --- a/packages/insomnia/src/sync/store/drivers/file-system-driver.ts +++ b/packages/insomnia/src/sync/store/drivers/file-system-driver.ts @@ -35,13 +35,13 @@ export default class FileSystemDriver implements BaseDriver { async setItem(key: string, value: Buffer) { console.log(`[FileSystemDriver] Writing to ${key}`); const finalPath = await this._getKeyPath(key); - // Temp path contains randomness to avoid race-condition collisions. This - // doesn't actually avoid race conditions but at least it won't fail. + // Temp path contains randomness to avoid race-condition collisions. This + // doesn't actually avoid race conditions but at least it won't fail. const tmpPath = `${finalPath}.${crypto.randomUUID()}.tmp`; console.log(`[FileSystemDriver] Writing to ${tmpPath} then renaming to ${finalPath}`); - // This method implements atomic writes by first writing to a temporary - // file (non-atomic) then renaming the file to the final value (atomic) + // This method implements atomic writes by first writing to a temporary + // file (non-atomic) then renaming the file to the final value (atomic) try { await fs.writeFile(tmpPath, value, 'utf8'); await gracefulRename(tmpPath, finalPath); diff --git a/packages/insomnia/src/sync/store/drivers/graceful-rename.ts b/packages/insomnia/src/sync/store/drivers/graceful-rename.ts index 58abd16f6f..f550c8b31b 100644 --- a/packages/insomnia/src/sync/store/drivers/graceful-rename.ts +++ b/packages/insomnia/src/sync/store/drivers/graceful-rename.ts @@ -13,7 +13,13 @@ function wait(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } -async function renameWithRetry(source: string, target: string, startTime: number, retryTimeout: number, attempt = 0): Promise { +async function renameWithRetry( + source: string, + target: string, + startTime: number, + retryTimeout: number, + attempt = 0, +): Promise { try { return await fs.rename(source, target); } catch (error) { @@ -52,10 +58,7 @@ async function renameWithRetry(source: string, target: string, startTime: number } } -export async function gracefulRename( - from: string, - to: string, -) { +export async function gracefulRename(from: string, to: string) { if (isWindows()) { return renameWithRetry(from, to, Date.now(), WINDOWS_RENAME_TIMEOUT); } diff --git a/packages/insomnia/src/sync/vcs/__tests__/initialize-backend-project.test.ts b/packages/insomnia/src/sync/vcs/__tests__/initialize-backend-project.test.ts index c5767e33a6..62a6ecbdaa 100644 --- a/packages/insomnia/src/sync/vcs/__tests__/initialize-backend-project.test.ts +++ b/packages/insomnia/src/sync/vcs/__tests__/initialize-backend-project.test.ts @@ -6,7 +6,6 @@ import { pushSnapshotOnInitialize } from '../initialize-backend-project'; import { VCS } from '../vcs'; describe('initialize-backend-project', () => { - describe('pushSnapshotOnInitialize()', () => { const vcs = new VCS(new MemoryDriver()); diff --git a/packages/insomnia/src/sync/vcs/__tests__/util.test.ts b/packages/insomnia/src/sync/vcs/__tests__/util.test.ts index ae20eb6a2d..4ec71e84ca 100644 --- a/packages/insomnia/src/sync/vcs/__tests__/util.test.ts +++ b/packages/insomnia/src/sync/vcs/__tests__/util.test.ts @@ -35,23 +35,28 @@ describe('util', () => { workspaceModelBuilder.reset(); }); - const DA1 = statusCandidateBuilder.reset() + const DA1 = statusCandidateBuilder + .reset() .key('a') .document(baseModelBuilder.reset()._id('a').name('1').build()) .build(); - const DA2 = statusCandidateBuilder.reset() + const DA2 = statusCandidateBuilder + .reset() .key('a') .document(baseModelBuilder.reset()._id('a').name('2').build()) .build(); - const DB1 = statusCandidateBuilder.reset() + const DB1 = statusCandidateBuilder + .reset() .key('b') .document(baseModelBuilder.reset()._id('b').name('1').build()) .build(); - const DB2 = statusCandidateBuilder.reset() + const DB2 = statusCandidateBuilder + .reset() .key('b') .document(baseModelBuilder.reset()._id('b').name('2').build()) .build(); - const DC1 = statusCandidateBuilder.reset() + const DC1 = statusCandidateBuilder + .reset() .key('c') .document(baseModelBuilder.reset()._id('c').name('1').build()) .build(); @@ -90,10 +95,7 @@ describe('util', () => { describe('generateSnapshotStateMap()', () => { it('generates from simple states', async () => { - const snapshot = newSnapshot(1, [ - newStateEntry('doc_1', 'blob_1'), - newStateEntry('doc_2', 'blob_2'), - ]); + const snapshot = newSnapshot(1, [newStateEntry('doc_1', 'blob_1'), newStateEntry('doc_2', 'blob_2')]); const map = generateSnapshotStateMap(snapshot); expect(Object.keys(map).sort()).toEqual(['doc_1', 'doc_2']); }); @@ -117,11 +119,7 @@ describe('util', () => { describe('generateCandidateMap()', () => { it('generates from simple candidates', async () => { - const candidates = [ - newCandidate('doc_2', 2), - newCandidate('doc_1', 1), - newCandidate('doc_2', 2), - ]; + const candidates = [newCandidate('doc_2', 2), newCandidate('doc_1', 1), newCandidate('doc_2', 2)]; const map = generateCandidateMap(candidates); expect(Object.keys(map).sort()).toEqual(['doc_1', 'doc_2']); }); @@ -576,34 +574,24 @@ describe('util', () => { it('does it', () => { const state = [A1, B1, E1, F1]; const conflicts = [ - mergeConflictBuilder.reset() + mergeConflictBuilder + .reset() .key(A1.key) .name(A1.name) .mineBlob(A1.blob) .theirsBlob(A2.blob) .choose(A1.blob) .build(), - mergeConflictBuilder.reset() - .key(B1.key) - .name(B1.name) - .mineBlob(B1.blob) - .theirsBlob(null) - .choose(null) - .build(), - mergeConflictBuilder.reset() + mergeConflictBuilder.reset().key(B1.key).name(B1.name).mineBlob(B1.blob).theirsBlob(null).choose(null).build(), + mergeConflictBuilder + .reset() .key(C1.key) .name(C1.name) .mineBlob(null) .theirsBlob(C1.blob) .choose(C1.blob) .build(), - mergeConflictBuilder.reset() - .key(D1.key) - .name(D1.name) - .mineBlob(D1.blob) - .theirsBlob(null) - .choose(null) - .build(), + mergeConflictBuilder.reset().key(D1.key).name(D1.name).mineBlob(D1.blob).theirsBlob(null).choose(null).build(), ]; expect(updateStateWithConflictResolutions(state, conflicts)).toEqual([A1, E1, F1, C1]); }); @@ -872,12 +860,8 @@ describe('util', () => { expect(result1.hash).toBe(result2.hash); expect(result1.hash).toBe('7eaaa8a03bada54b403aeada681aad6892a28ab3'); expect(result2.hash).toBe('7eaaa8a03bada54b403aeada681aad6892a28ab3'); - expect(result1.content).toBe( - '{"arr":[{"a":"a","b":"b"}],"obj":{"obj2":{"a":"a","b":"b"}}}', - ); - expect(result2.content).toBe( - '{"arr":[{"a":"a","b":"b"}],"obj":{"obj2":{"a":"a","b":"b"}}}', - ); + expect(result1.content).toBe('{"arr":[{"a":"a","b":"b"}],"obj":{"obj2":{"a":"a","b":"b"}}}'); + expect(result2.content).toBe('{"arr":[{"a":"a","b":"b"}],"obj":{"obj2":{"a":"a","b":"b"}}}'); }); it('array order matters', () => { @@ -930,15 +914,9 @@ describe('util', () => { }); it('ignores global object keys that do not matter', () => { - const result1 = hashDocument( - baseModelBuilder.modified(123).parentId('abc').build(), - ); - const result2 = hashDocument( - baseModelBuilder.modified(456).build(), - ); - const result3 = hashDocument( - baseModelBuilder.name('abc').modified(456).build(), - ); + const result1 = hashDocument(baseModelBuilder.modified(123).parentId('abc').build()); + const result2 = hashDocument(baseModelBuilder.modified(456).build()); + const result3 = hashDocument(baseModelBuilder.name('abc').modified(456).build()); expect(result1.hash).toBe(result2.hash); expect(result1.hash).not.toBe(result3.hash); }); @@ -956,12 +934,8 @@ describe('util', () => { const original = hash(originalSyncedWorkspace); // Act - const withParent = hashDocument( - workspaceModelBuilder.parentId('abc').build(), - ); - const unique = hashDocument( - workspaceModelBuilder.name('unique').parentId('abc').build(), - ); + const withParent = hashDocument(workspaceModelBuilder.parentId('abc').build()); + const unique = hashDocument(workspaceModelBuilder.name('unique').parentId('abc').build()); // Assert expect(original.hash).toBe(withParent.hash); @@ -990,12 +964,13 @@ function newStateEntry(key, blob) { }; } -const newCandidate = (key: string, n: number) => statusCandidateBuilder - .reset() - .key(key) - .name(`Candidate ${n}`) - .document(baseModelBuilder.reset().name(`Content for candidate ${key}.${n}`).build()) - .build(); +const newCandidate = (key: string, n: number) => + statusCandidateBuilder + .reset() + .key(key) + .name(`Candidate ${n}`) + .document(baseModelBuilder.reset().name(`Content for candidate ${key}.${n}`).build()) + .build(); const newBranch = (snapshots: string[]) => branchBuilder.snapshots(snapshots).build(); @@ -1004,34 +979,39 @@ describe('interceptAccessError', () => { // Arrange // Act - const action = async () => await interceptAccessError({ - action: 'action', - callback: () => { - throw new Error('DANGER! invalid access to the fifth dimensional nebulo 9.'); - }, - resourceName: 'resourceName', - resourceType: 'resourceType', - }) as Error; + const action = async () => + (await interceptAccessError({ + action: 'action', + callback: () => { + throw new Error('DANGER! invalid access to the fifth dimensional nebulo 9.'); + }, + resourceName: 'resourceName', + resourceType: 'resourceType', + })) as Error; // Assert const result = expect(action).rejects; result.toBeInstanceOf(Error); - result.toThrowError('You no longer have permission to action the "resourceName" resourceType. Contact your team administrator if you think this is an error.'); + result.toThrowError( + 'You no longer have permission to action the "resourceName" resourceType. Contact your team administrator if you think this is an error.', + ); }); - it('does not intercept errors it doesn\'t care about', async () => { + it("does not intercept errors it doesn't care about", async () => { // Arrange - const message = 'Having been rejected by the planet smasher, Ziltoid seeks the council of the omnidimensional creator.'; + const message = + 'Having been rejected by the planet smasher, Ziltoid seeks the council of the omnidimensional creator.'; // Act - const action = async () => await interceptAccessError({ - action: 'action', - callback: () => { - throw new Error(message); - }, - resourceName: 'resourceName', - resourceType: 'resourceType', - }) as Error; + const action = async () => + (await interceptAccessError({ + action: 'action', + callback: () => { + throw new Error(message); + }, + resourceName: 'resourceName', + resourceType: 'resourceType', + })) as Error; // Assert const result = expect(action).rejects; diff --git a/packages/insomnia/src/sync/vcs/__tests__/vcs.test.ts b/packages/insomnia/src/sync/vcs/__tests__/vcs.test.ts index 02e4554436..7f668b5e41 100644 --- a/packages/insomnia/src/sync/vcs/__tests__/vcs.test.ts +++ b/packages/insomnia/src/sync/vcs/__tests__/vcs.test.ts @@ -32,20 +32,18 @@ describe('VCS', () => { describe('status()', () => { it('returns status with no commits', async () => { const v = await vcs('master'); - const status = await v.status( - [ - { - key: 'foo', - name: 'Foo', - document: newDoc('bar'), - }, - { - key: 'baz', - name: 'Baz', - document: newDoc('qux'), - }, - ], - ); + const status = await v.status([ + { + key: 'foo', + name: 'Foo', + document: newDoc('bar'), + }, + { + key: 'baz', + name: 'Baz', + document: newDoc('qux'), + }, + ]); expect(status).toEqual({ key: '6dbc95d09d310cf9d8561bc46da440d8197c3bf1', stage: {}, @@ -159,7 +157,8 @@ describe('VCS', () => { blobId: '4a1582f059cf4cc4c4dcd56e893b9ae88f32125d', key: 'a', name: 'A', - previousBlobContent: '{\"_id\":\"aaa\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', + previousBlobContent: + '{\"_id\":\"aaa\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', }, notA: { added: true, @@ -174,8 +173,10 @@ describe('VCS', () => { key: 'c', name: 'C', blobId: '87a13a793c6bc2137732ba4f8dc8d877fc143bad', - blobContent: '{"_id":"modified","created":1234,"isPrivate":false,"name":"name","parentId":"","type":"base"}', - previousBlobContent: '{\"_id\":\"ccc\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', + blobContent: + '{"_id":"modified","created":1234,"isPrivate":false,"name":"name","parentId":"","type":"base"}', + previousBlobContent: + '{\"_id\":\"ccc\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', }, d: { added: true, @@ -187,36 +188,29 @@ describe('VCS', () => { }, }, }); - await v.stage([ - status.unstaged.a, - status.unstaged.notA, - status.unstaged.c, - status.unstaged.d, + await v.stage([status.unstaged.a, status.unstaged.notA, status.unstaged.c, status.unstaged.d]); + const status2 = await v.status([ + { + key: 'notA', + name: 'Not A', + document: newDoc('aaa'), + }, + { + key: 'b', + name: 'B', + document: newDoc('bbb'), + }, + { + key: 'c', + name: 'C', + document: newDoc('modified'), + }, + { + key: 'd', + name: 'D', + document: newDoc('ddd'), + }, ]); - const status2 = await v.status( - [ - { - key: 'notA', - name: 'Not A', - document: newDoc('aaa'), - }, - { - key: 'b', - name: 'B', - document: newDoc('bbb'), - }, - { - key: 'c', - name: 'C', - document: newDoc('modified'), - }, - { - key: 'd', - name: 'D', - document: newDoc('ddd'), - }, - ] - ); expect(status2).toEqual({ key: '872dd92bb678f7e26b8610e4d37c0438f2f04beb', stage: { @@ -225,7 +219,8 @@ describe('VCS', () => { blobId: '4a1582f059cf4cc4c4dcd56e893b9ae88f32125d', key: 'a', name: 'A', - previousBlobContent: '{\"_id\":\"aaa\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', + previousBlobContent: + '{\"_id\":\"aaa\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', }, notA: { added: true, @@ -240,8 +235,10 @@ describe('VCS', () => { blobId: '87a13a793c6bc2137732ba4f8dc8d877fc143bad', key: 'c', name: 'C', - blobContent: '{"_id":"modified","created":1234,"isPrivate":false,"name":"name","parentId":"","type":"base"}', - previousBlobContent: '{\"_id\":\"ccc\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', + blobContent: + '{"_id":"modified","created":1234,"isPrivate":false,"name":"name","parentId":"","type":"base"}', + previousBlobContent: + '{\"_id\":\"ccc\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', }, d: { added: true, @@ -274,20 +271,18 @@ describe('VCS', () => { {}, ); await v.stage([status.unstaged.a]); - const status2 = await v.status( - [ - { - key: 'a', - name: 'A', - document: newDoc('modified'), - }, - { - key: 'b', - name: 'B', - document: newDoc('bbb'), - }, - ], - ); + const status2 = await v.status([ + { + key: 'a', + name: 'A', + document: newDoc('modified'), + }, + { + key: 'b', + name: 'B', + document: newDoc('bbb'), + }, + ]); expect(status2).toEqual({ key: '7e7b488b9010839218f8e8c7d1d48b0e0e6b5f8c', stage: { @@ -306,8 +301,10 @@ describe('VCS', () => { blobId: '87a13a793c6bc2137732ba4f8dc8d877fc143bad', key: 'a', name: 'A', - blobContent: '{"_id":"modified","created":1234,"isPrivate":false,"name":"name","parentId":"","type":"base"}', - previousBlobContent: '{\"_id\":\"aaa\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', + blobContent: + '{"_id":"modified","created":1234,"isPrivate":false,"name":"name","parentId":"","type":"base"}', + previousBlobContent: + '{\"_id\":\"aaa\",\"created\":1234,\"isPrivate\":false,\"name\":\"name\",\"parentId\":\"\",\"type\":\"base\"}', }, b: { added: true, @@ -382,20 +379,18 @@ describe('VCS', () => { previousBlobContent: 'null', }, }); - const status2 = await v.status( - [ - { - key: 'foo', - name: 'Foo', - document: newDoc('bar'), - }, - { - key: 'baz', - name: 'Baz', - document: newDoc('qux'), - }, - ], - ); + const status2 = await v.status([ + { + key: 'foo', + name: 'Foo', + document: newDoc('bar'), + }, + { + key: 'baz', + name: 'Baz', + document: newDoc('qux'), + }, + ]); expect(status2).toEqual({ key: 'cfd47b8a7d50f39dfa1ca956ac2ab60427d6351b', stage: { @@ -459,15 +454,13 @@ describe('VCS', () => { it('commits deleted entity', async () => { const v = await vcs('master'); - const status = await v.status( - [ - { - key: 'foo', - name: 'Foo', - document: newDoc('bar'), - }, - ], - ); + const status = await v.status([ + { + key: 'foo', + name: 'Foo', + document: newDoc('bar'), + }, + ]); await v.stage([status.unstaged.foo]); await v.takeSnapshot('Add foo'); const history = await v.getHistory(); @@ -562,15 +555,13 @@ describe('VCS', () => { it('remove branch', async () => { const v = await vcs('master'); // Add something to master - const status1 = await v.status( - [ - { - key: 'foo', - name: 'Foo', - document: newDoc('bar'), - }, - ], - ); + const status1 = await v.status([ + { + key: 'foo', + name: 'Foo', + document: newDoc('bar'), + }, + ]); await v.stage([status1.unstaged.foo]); await v.takeSnapshot('Add foo'); // Checkout branch @@ -654,25 +645,23 @@ describe('VCS', () => { ]); await v.fork('feature-a'); await v.checkout([], 'feature-a'); - const status2 = await v.status( - [ - { - key: 'a', - name: 'A', - document: newDoc('aaa'), - }, - { - key: 'b', - name: 'B', - document: newDoc('bbbbbbb'), - }, - { - key: 'c', - name: 'C', - document: newDoc('ccc'), - }, - ] - ); + const status2 = await v.status([ + { + key: 'a', + name: 'A', + document: newDoc('aaa'), + }, + { + key: 'b', + name: 'B', + document: newDoc('bbbbbbb'), + }, + { + key: 'c', + name: 'C', + document: newDoc('ccc'), + }, + ]); await v.stage([status2.unstaged.b, status2.unstaged.c]); await v.takeSnapshot('Add C, modify B'); expect((await v.getHistory())[1].state).toEqual( diff --git a/packages/insomnia/src/sync/vcs/initialize-backend-project.ts b/packages/insomnia/src/sync/vcs/initialize-backend-project.ts index 1477bcfca2..16709496cf 100644 --- a/packages/insomnia/src/sync/vcs/initialize-backend-project.ts +++ b/packages/insomnia/src/sync/vcs/initialize-backend-project.ts @@ -6,16 +6,24 @@ import type { Workspace } from '../../models/workspace'; import type { StatusCandidate } from '../types'; import type { VCS } from './vcs'; -export const initializeLocalBackendProjectAndMarkForSync = async ({ vcs, workspace }: { vcs: VCS; workspace: Workspace }) => { +export const initializeLocalBackendProjectAndMarkForSync = async ({ + vcs, + workspace, +}: { + vcs: VCS; + workspace: Workspace; +}) => { // Create local project await vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name); // Everything unstaged - const candidates = (await database.withDescendants(workspace)).filter(canSync).map((doc: BaseModel): StatusCandidate => ({ - key: doc._id, - name: doc.name || '', - document: doc, - })); + const candidates = (await database.withDescendants(workspace)).filter(canSync).map( + (doc: BaseModel): StatusCandidate => ({ + key: doc._id, + name: doc.name || '', + document: doc, + }), + ); const status = await vcs.status(candidates); // Stage everything diff --git a/packages/insomnia/src/sync/vcs/insomnia-sync.ts b/packages/insomnia/src/sync/vcs/insomnia-sync.ts index 9854b9ad72..75fc53b6f6 100644 --- a/packages/insomnia/src/sync/vcs/insomnia-sync.ts +++ b/packages/insomnia/src/sync/vcs/insomnia-sync.ts @@ -17,9 +17,7 @@ export const VCSInstance = () => { if (vcs) { return vcs; } - const driver = FileSystemDriver.create( - process.env['INSOMNIA_DATA_PATH'] || window.app.getPath('userData'), - ); + const driver = FileSystemDriver.create(process.env['INSOMNIA_DATA_PATH'] || window.app.getPath('userData')); vcs = new VCS(driver, async (conflicts, labels) => { return new Promise((resolve, reject) => { showModal(SyncMergeModal, { diff --git a/packages/insomnia/src/sync/vcs/migrate-projects-into-organization.ts b/packages/insomnia/src/sync/vcs/migrate-projects-into-organization.ts index a85bd737b4..6490e09568 100644 --- a/packages/insomnia/src/sync/vcs/migrate-projects-into-organization.ts +++ b/packages/insomnia/src/sync/vcs/migrate-projects-into-organization.ts @@ -33,8 +33,8 @@ export const shouldMigrateProjectUnderOrganization = async () => { export const migrateProjectsIntoOrganization = async ({ personalOrganizationId, }: { - personalOrganizationId: string; - }) => { + personalOrganizationId: string; +}) => { // Legacy remote projects without organizations // Local projects without organizations except scratchpad const [legacyRemoteProjects, localProjects] = await Promise.all([ @@ -56,7 +56,7 @@ export const migrateProjectsIntoOrganization = async ({ models.project.update(remoteProject, { parentId: remoteProject.remoteId, remoteId: remoteProject._id, - }) + }), ); } @@ -65,7 +65,7 @@ export const migrateProjectsIntoOrganization = async ({ updatePromises.push( models.project.update(localProject, { parentId: personalOrganizationId, - }) + }), ); } diff --git a/packages/insomnia/src/sync/vcs/pull-backend-project.ts b/packages/insomnia/src/sync/vcs/pull-backend-project.ts index 49f40095ee..95c9a01f8f 100644 --- a/packages/insomnia/src/sync/vcs/pull-backend-project.ts +++ b/packages/insomnia/src/sync/vcs/pull-backend-project.ts @@ -28,22 +28,24 @@ export const pullBackendProject = async ({ vcs, backendProject, remoteProject }: // @TODO Revisit the UX for this. What should happen if there are other branches? // The default branch does not exist, so we create it and the workspace locally if (defaultBranchMissing) { - const workspace = await models.initModel( - models.workspace.type, - { - _id: backendProject.rootDocumentId, - name: backendProject.name, - parentId: remoteProject._id, - scope: 'collection', - }, - ); + const workspace = await models.initModel(models.workspace.type, { + _id: backendProject.rootDocumentId, + name: backendProject.name, + parentId: remoteProject._id, + scope: 'collection', + }); await database.upsert(workspace); return { project: remoteProject, workspaceId: workspace._id }; } - await vcs.pull({ candidates: [], teamId: remoteProject.parentId, teamProjectId: remoteProject._id, projectId: remoteProject._id }); // There won't be any existing docs since it's a new pull + await vcs.pull({ + candidates: [], + teamId: remoteProject.parentId, + teamProjectId: remoteProject._id, + projectId: remoteProject._id, + }); // There won't be any existing docs since it's a new pull const flushId = await database.bufferChanges(); let workspaceId; @@ -60,5 +62,4 @@ export const pullBackendProject = async ({ vcs, backendProject, remoteProject }: await database.flushChanges(flushId); return { project: remoteProject, workspaceId }; - }; diff --git a/packages/insomnia/src/sync/vcs/util.ts b/packages/insomnia/src/sync/vcs/util.ts index dd5eb2015d..506591b676 100644 --- a/packages/insomnia/src/sync/vcs/util.ts +++ b/packages/insomnia/src/sync/vcs/util.ts @@ -51,9 +51,7 @@ export function generateCandidateMap(candidates: StatusCandidate[]): StatusCandi return map; } -export function combinedMapKeys( - ...maps: T[] -): DocumentKey[] { +export function combinedMapKeys(...maps: T[]): DocumentKey[] { const keyMap: Record = {}; for (const map of maps) { @@ -239,10 +237,7 @@ export function threeWayMerge( }; } -export function compareBranches( - a: Branch | null, - b: Branch | null, -): Compare { +export function compareBranches(a: Branch | null, b: Branch | null): Compare { const snapshotsA = a ? a.snapshots : []; const snapshotsB = b ? b.snapshots : []; const latestA = snapshotsA[snapshotsA.length - 1] || null; @@ -285,10 +280,7 @@ export interface StateDelta { remove: SnapshotStateEntry[]; } -export function stateDelta( - base: SnapshotState, - desired: SnapshotState, -) { +export function stateDelta(base: SnapshotState, desired: SnapshotState) { const result: StateDelta = { add: [], update: [], @@ -391,11 +383,7 @@ export function getRootSnapshot(a: Branch | null, b: Branch | null): string | nu return rootSnapshotId || null; } -export function preMergeCheck( - trunkState: SnapshotState, - otherState: SnapshotState, - candidates: StatusCandidate[], -) { +export function preMergeCheck(trunkState: SnapshotState, otherState: SnapshotState, candidates: StatusCandidate[]) { const conflicts: StatusCandidate[] = []; const dirty: StatusCandidate[] = []; const trunkMap = generateStateMap(trunkState); @@ -430,13 +418,7 @@ export function preMergeCheck( } // Candidate is different but trunk and other are the same (preserve safe change) - if ( - other && - trunk && - other.blob === trunk.blob && - blobId !== other.blob && - blobId !== trunk.blob - ) { + if (other && trunk && other.blob === trunk.blob && blobId !== other.blob && blobId !== trunk.blob) { dirty.push(candidate); continue; } @@ -553,24 +535,24 @@ export function describeChanges(a: T, b: T): string[] { return changes; } -export const interceptAccessError = async ( - { - callback, - action, - resourceName, - resourceType = strings.collection.singular.toLowerCase(), - }: { - callback: () => T | Promise; - action: string; - resourceName: string; - resourceType?: string; - } -) => { +export const interceptAccessError = async ({ + callback, + action, + resourceName, + resourceType = strings.collection.singular.toLowerCase(), +}: { + callback: () => T | Promise; + action: string; + resourceName: string; + resourceType?: string; +}) => { try { return await callback(); } catch (error: unknown) { if (error instanceof Error && error.message.includes('invalid access')) { - throw new Error(`You no longer have permission to ${action} the "${resourceName}" ${resourceType}. Contact your team administrator if you think this is an error.`); + throw new Error( + `You no longer have permission to ${action} the "${resourceName}" ${resourceType}. Contact your team administrator if you think this is an error.`, + ); } throw error; } diff --git a/packages/insomnia/src/sync/vcs/vcs.ts b/packages/insomnia/src/sync/vcs/vcs.ts index eee152bce2..2641d665e8 100644 --- a/packages/insomnia/src/sync/vcs/vcs.ts +++ b/packages/insomnia/src/sync/vcs/vcs.ts @@ -42,7 +42,10 @@ import { const EMPTY_HASH = crypto.createHash('sha1').digest('hex').replace(/./g, '0'); -type ConflictHandler = (conflicts: MergeConflict[], labels: { ours: string; theirs: string }) => Promise; +type ConflictHandler = ( + conflicts: MergeConflict[], + labels: { ours: string; theirs: string }, +) => Promise; // breaks one array into multiple arrays of size chunkSize export function chunkArray(arr: T[], chunkSize: number) { @@ -210,7 +213,6 @@ export class VCS { let previousBlobContent: BaseModel | null = null; try { previousBlobContent = await this.blobFromLastSnapshot(key); - } catch (e) { // No previous blob found } finally { @@ -295,10 +297,7 @@ export class VCS { static validateBranchName(branchName: string) { if (!branchName.match(/^[a-zA-Z0-9][a-zA-Z0-9-_.]{2,}$/)) { - return ( - 'Branch names must be at least 3 characters long and can only contain ' + - 'letters, numbers, - and _' - ); + return 'Branch names must be at least 3 characters long and can only contain ' + 'letters, numbers, - and _'; } return ''; @@ -359,9 +358,7 @@ export class VCS { async checkout(candidates: StatusCandidate[], branchName: string) { const branchCurrent = await this._getCurrentBranch(); - const latestSnapshotCurrent: Snapshot | null = await this._getLatestSnapshot( - branchCurrent.name, - ); + const latestSnapshotCurrent: Snapshot | null = await this._getLatestSnapshot(branchCurrent.name); const latestStateCurrent = latestSnapshotCurrent ? latestSnapshotCurrent.state : []; const branchNext = await this._getOrCreateBranch(branchName); const latestSnapshotNext: Snapshot | null = await this._getLatestSnapshot(branchNext.name); @@ -517,11 +514,7 @@ export class VCS { return branches.map(b => b.name); } - async merge( - candidates: StatusCandidate[], - otherBranchName: string, - snapshotMessage?: string, - ) { + async merge(candidates: StatusCandidate[], otherBranchName: string, snapshotMessage?: string) { const branch = await this._getCurrentBranch(); console.log(`[sync] Merged branch ${otherBranchName} into ${branch.name}`); return this._merge(candidates, branch.name, otherBranchName, snapshotMessage); @@ -581,19 +574,29 @@ export class VCS { console.log(`[sync] Created commit ${snapshot.id} (${name})`); } - async pull({ candidates, teamId, teamProjectId, projectId }: { candidates: StatusCandidate[]; teamId: string; teamProjectId: string; projectId: string }) { + async pull({ + candidates, + teamId, + teamProjectId, + projectId, + }: { + candidates: StatusCandidate[]; + teamId: string; + teamProjectId: string; + projectId: string; + }) { await this._getOrCreateRemoteBackendProject({ teamId, teamProjectId }); const localBranch = await this._getCurrentBranch(); const tmpBranchForRemote = await this.customFetch(localBranch.name + '.hidden', localBranch.name); // Merge branch and ensure that we use the remote's history when merging const message = `Synced latest changes from ${localBranch.name}`; - const delta = await this._merge( + const delta = (await this._merge( candidates, localBranch.name, tmpBranchForRemote.name, message, true, - ) as unknown as Operation; + )) as unknown as Operation; // Remove tmp branch await this._removeBranch(tmpBranchForRemote); console.log(`[sync] Pulled branch ${localBranch.name}`); @@ -620,7 +623,12 @@ export class VCS { return remoteProject; } - async _createRemoteProject({ rootDocumentId, name, teamId, teamProjectId }: BackendProject & { teamId: string; teamProjectId: string }) { + async _createRemoteProject({ + rootDocumentId, + name, + teamId, + teamProjectId, + }: BackendProject & { teamId: string; teamProjectId: string }) { if (!teamId) { throw new Error('teamId should be defined'); } @@ -734,11 +742,7 @@ export class VCS { const latestStateTrunk = latestSnapshotTrunk ? latestSnapshotTrunk.state : []; const latestStateOther = latestSnapshotOther ? latestSnapshotOther.state : []; // Perform pre-merge checks - const { conflicts: preConflicts, dirty } = preMergeCheck( - latestStateTrunk, - latestStateOther, - candidates, - ); + const { conflicts: preConflicts, dirty } = preMergeCheck(latestStateTrunk, latestStateOther, candidates); if (preConflicts.length) { console.log('[sync] Merge failed', preConflicts); @@ -747,8 +751,7 @@ export class VCS { const shouldDoNothing1 = latestSnapshotOther && latestSnapshotOther.id === rootSnapshotId; const shouldDoNothing2 = branchOther.snapshots.length === 0; - const shouldFastForward1 = - rootSnapshot && (!latestSnapshotTrunk || rootSnapshot.id === latestSnapshotTrunk.id); + const shouldFastForward1 = rootSnapshot && (!latestSnapshotTrunk || rootSnapshot.id === latestSnapshotTrunk.id); const shouldFastForward2 = branchTrunk.snapshots.length === 0; if (shouldDoNothing1 || shouldDoNothing2) { @@ -766,23 +769,31 @@ export class VCS { latestStateOther, ); // Update state with conflict resolutions applied - const conflictsWithContent = await Promise.all(mergeConflicts.map(async conflict => { - let mineBlobContent: BaseModel | null = null; - let theirsBlobContent: BaseModel | null = null; - try { - mineBlobContent = conflict.mineBlob ? await this._getBlob(conflict.mineBlob) : null; - theirsBlobContent = conflict.theirsBlob ? await this._getBlob(conflict.theirsBlob) : null; - } catch (e) { - // No previous blob found - } - return { - ...conflict, - mineBlobContent, - theirsBlobContent, - }; - })); + const conflictsWithContent = await Promise.all( + mergeConflicts.map(async conflict => { + let mineBlobContent: BaseModel | null = null; + let theirsBlobContent: BaseModel | null = null; + try { + mineBlobContent = conflict.mineBlob ? await this._getBlob(conflict.mineBlob) : null; + theirsBlobContent = conflict.theirsBlob ? await this._getBlob(conflict.theirsBlob) : null; + } catch (e) { + // No previous blob found + } + return { + ...conflict, + mineBlobContent, + theirsBlobContent, + }; + }), + ); - const conflictResolutions = await this.handleAnyConflicts(conflictsWithContent, otherBranchName.includes('.hidden') ? { ours: `${trunkBranchName} local`, theirs: `${otherBranchName.replace('.hidden', '')} remote` } : { ours: trunkBranchName, theirs: otherBranchName }, ''); + const conflictResolutions = await this.handleAnyConflicts( + conflictsWithContent, + otherBranchName.includes('.hidden') + ? { ours: `${trunkBranchName} local`, theirs: `${otherBranchName.replace('.hidden', '')} remote` } + : { ours: trunkBranchName, theirs: otherBranchName }, + '', + ); const state = updateStateWithConflictResolutions(stateBeforeConflicts, conflictResolutions); @@ -807,14 +818,8 @@ export class VCS { }; } - async _createSnapshotFromState( - branch: Branch, - state: SnapshotState, - name: string, - ) { - const parentId = branch.snapshots.length - ? branch.snapshots[branch.snapshots.length - 1] - : EMPTY_HASH; + async _createSnapshotFromState(branch: Branch, state: SnapshotState, name: string) { + const parentId = branch.snapshots.length ? branch.snapshots[branch.snapshots.length - 1] : EMPTY_HASH; // Create the snapshot const id = _generateSnapshotID(parentId, this._backendProjectId(), state); @@ -838,11 +843,7 @@ export class VCS { return snapshot; } - async _runGraphQL( - query: string, - variables: Record, - name: string, - ): Promise { + async _runGraphQL(query: string, variables: Record, name: string): Promise { const { sessionId } = await this._assertSession(); const { data, errors } = await insomniaFetch<{ data: T; errors: [{ message: string }] }>({ @@ -1140,9 +1141,7 @@ export class VCS { return project; } - async _queryTeamMemberKeys( - teamId: string, - ): Promise<{ + async _queryTeamMemberKeys(teamId: string): Promise<{ memberKeys: { accountId: string; publicKey: string; @@ -1501,7 +1500,10 @@ export class VCS { } branch.modified = new Date(); - return this._store.setItem(`/projects/${this._backendProjectId()}/branches/${branch.name.toLowerCase()}.json`, branch); + return this._store.setItem( + `/projects/${this._backendProjectId()}/branches/${branch.name.toLowerCase()}.json`, + branch, + ); } async _removeBranch(branch: Branch) { @@ -1518,7 +1520,9 @@ export class VCS { } _getBlob(blobId: string) { - return this._store.getItem(`/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`) as Promise; + return this._store.getItem( + `/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, + ) as Promise; } async _getBlobs(ids: string[]) { @@ -1532,7 +1536,10 @@ export class VCS { } async _storeBlob(blobId: string, content: Record | null) { - return this._store.setItem(`/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, content); + return this._store.setItem( + `/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, + content, + ); } async _storeBlobs(blobsToStore: Record) { @@ -1540,7 +1547,12 @@ export class VCS { for (const blobId of Object.keys(blobsToStore)) { const buff = Buffer.from(blobsToStore[blobId], 'utf8'); - promises.push(this._store.setItem(`/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, buff)); + promises.push( + this._store.setItem( + `/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, + buff, + ), + ); } await Promise.all(promises); @@ -1550,14 +1562,21 @@ export class VCS { const promises: Promise[] = []; for (const blobId of Object.keys(decryptedBlobs)) { - promises.push(this._store.setItemRaw(`/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, decryptedBlobs[blobId])); + promises.push( + this._store.setItemRaw( + `/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, + decryptedBlobs[blobId], + ), + ); } await Promise.all(promises); } async _getBlobRaw(blobId: string) { - return this._store.getItemRaw(`/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`); + return this._store.getItemRaw( + `/projects/${this._backendProjectId()}/blobs/${blobId.slice(0, 2)}/${blobId.slice(2)}`, + ); } async _hasBlob(blobId: string) { diff --git a/packages/insomnia/src/templating/__tests__/utils.test.ts b/packages/insomnia/src/templating/__tests__/utils.test.ts index d505513d43..60efcd3698 100644 --- a/packages/insomnia/src/templating/__tests__/utils.test.ts +++ b/packages/insomnia/src/templating/__tests__/utils.test.ts @@ -27,7 +27,6 @@ describe('normalizeToDotAndBracketNotation()', () => { }); }); describe('getKeys()', () => { - it('flattens complex object', () => { const obj = { foo: 'bar', @@ -111,7 +110,6 @@ describe('getKeys()', () => { }); describe('tokenizeTag()', () => { - it('tokenizes complex tag', () => { const actual = utils.tokenizeTag('{% name bar, "baz \\"qux\\"" , 1 + 5 | default("foo") %}'); const expected = { @@ -245,7 +243,6 @@ describe('tokenizeTag()', () => { }); describe('unTokenizeTag()', () => { - it('handles the default case', () => { const tagStr = '{% name bar, "baz \\"qux\\"" , 1 + 5, \'hi\' %}'; const tagData = utils.tokenizeTag(tagStr); @@ -292,9 +289,7 @@ describe('unTokenizeTag()', () => { ], }; const result = utils.unTokenizeTag(tagData); - expect(result).toEqual( - "{% name true, 'foo', foo.length, 'foo/bar/baz', 'id_123', 10, 'foo', var %}", - ); + expect(result).toEqual("{% name true, 'foo', foo.length, 'foo/bar/baz', 'id_123', 10, 'foo', var %}"); }); it('fixes missing quotedBy attribute', () => { @@ -317,7 +312,6 @@ describe('unTokenizeTag()', () => { }); describe('encodeEncoding()', () => { - it('encodes things', () => { expect(utils.encodeEncoding('hello', 'base64')).toBe('b64::aGVsbG8=::46b'); expect(utils.encodeEncoding(null, 'base64')).toBe(null); @@ -327,7 +321,6 @@ describe('encodeEncoding()', () => { }); describe('decodeEncoding()', () => { - it('encodes things', () => { expect(utils.decodeEncoding('b64::aGVsbG8=::46b')).toBe('hello'); expect(utils.decodeEncoding('aGVsbG8=')).toBe('aGVsbG8='); @@ -338,7 +331,6 @@ describe('decodeEncoding()', () => { }); describe('extractUndefinedVariableKey()', () => { - it('extract nunjucks variable key', () => { expect(extractUndefinedVariableKey('{{name}}', {})).toEqual(['name']); expect(extractUndefinedVariableKey('{{name}}', { name: '' })).toEqual([]); diff --git a/packages/insomnia/src/templating/base-extension-worker.ts b/packages/insomnia/src/templating/base-extension-worker.ts index 0fa4abacf1..beaa98f28d 100644 --- a/packages/insomnia/src/templating/base-extension-worker.ts +++ b/packages/insomnia/src/templating/base-extension-worker.ts @@ -29,9 +29,7 @@ export default class BaseExtension { this._ext = ext; this._plugin = plugin; const tag = this.getTag(); - this.tags = [ - ...(tag === null ? [] : [tag]), - ]; + this.tags = [...(tag === null ? [] : [tag])]; } getTag() { @@ -51,10 +49,7 @@ export default class BaseExtension { } getLiveDisplayName() { - return ( - this._ext?.liveDisplayName || - (() => '') - ); + return this._ext?.liveDisplayName || (() => ''); } getDisablePreview() { @@ -264,8 +259,10 @@ export default class BaseExtension { const latest = await resp.json(); return latest; }, - getBodyBuffer: async (response?: { bodyPath?: string; bodyCompression?: 'zip' | null | '__NEEDS_MIGRATION__' | undefined }, - readFailureValue?: string) => { + getBodyBuffer: async ( + response?: { bodyPath?: string; bodyCompression?: 'zip' | null | '__NEEDS_MIGRATION__' | undefined }, + readFailureValue?: string, + ) => { const resp = await fetch('insomnia-templating-worker-database://response.getBodyBuffer', { method: 'post', body: JSON.stringify({ response, readFailureValue }), diff --git a/packages/insomnia/src/templating/base-extension.ts b/packages/insomnia/src/templating/base-extension.ts index c7123e9544..15c285f98f 100644 --- a/packages/insomnia/src/templating/base-extension.ts +++ b/packages/insomnia/src/templating/base-extension.ts @@ -20,9 +20,7 @@ export default class BaseExtension { this._ext = ext; this._plugin = plugin; const tag = this.getTag(); - this.tags = [ - ...(tag === null ? [] : [tag]), - ]; + this.tags = [...(tag === null ? [] : [tag])]; } getTag() { @@ -42,9 +40,7 @@ export default class BaseExtension { } getLiveDisplayName() { - return ( - this._ext?.liveDisplayName || (() => '') - ); + return this._ext?.liveDisplayName || (() => ''); } getDisablePreview() { diff --git a/packages/insomnia/src/templating/index.ts b/packages/insomnia/src/templating/index.ts index 8cb4a0da85..0691b695c0 100644 --- a/packages/insomnia/src/templating/index.ts +++ b/packages/insomnia/src/templating/index.ts @@ -68,9 +68,7 @@ export function render( const location = err.message.match(/\[Line (\d+), Column (\d+)*]/); const line = location ? parseInt(location[1]) : 1; const column = location ? parseInt(location[2]) : 1; - const reason = err.message.includes('attempted to output null or undefined value') - ? 'undefined' - : 'error'; + const reason = err.message.includes('attempted to output null or undefined value') ? 'undefined' : 'error'; const newError = new RenderError(sanitizedMsg); newError.path = path || ''; newError.message = sanitizedMsg; diff --git a/packages/insomnia/src/templating/third_party/objectPath.ts b/packages/insomnia/src/templating/third_party/objectPath.ts index 11e930d27d..b65171d823 100644 --- a/packages/insomnia/src/templating/third_party/objectPath.ts +++ b/packages/insomnia/src/templating/third_party/objectPath.ts @@ -6,115 +6,125 @@ https://github.com/Kong/insomnia/issues/7286 objectPath.parse could enter endless loop if last char in key name is backslash. */ -type Quote = '\'' | '\"'; +type Quote = "'" | '\"'; const regex = { - "'": /\\\'/g, - '"': /\\\"/g, + "'": /\\\'/g, + '"': /\\\"/g, }; const ObjectPath = { - parse: function (str: string) { - if (typeof str !== 'string') { - throw new TypeError('ObjectPath.parse must be passed a string'); + parse: function (str: string) { + if (typeof str !== 'string') { + throw new TypeError('ObjectPath.parse must be passed a string'); + } + + let i = 0; + const parts = []; + let dot, bracket, quote, closing; + while (i < str.length) { + dot = str.indexOf('.', i); + bracket = str.indexOf('[', i); + + if (dot === -1 && bracket === -1) { + // we've reached the end + parts.push(str.slice(i, str.length)); + i = str.length; + } else if (bracket === -1 || (dot !== -1 && dot < bracket)) { + // dots + parts.push(str.slice(i, dot)); + i = dot + 1; + } else { + // brackets + if (bracket > i) { + parts.push(str.slice(i, bracket)); + i = bracket; } + quote = str.slice(bracket + 1, bracket + 2); - let i = 0; - const parts = []; - let dot, bracket, quote, closing; - while (i < str.length) { - dot = str.indexOf('.', i); - bracket = str.indexOf('[', i); + if (quote !== '"' && quote !== "'") { + closing = str.indexOf(']', bracket); + if (closing === -1) { + closing = str.length; + } + parts.push(str.slice(i + 1, closing)); + i = str.slice(closing + 1, closing + 2) === '.' ? closing + 2 : closing + 1; + } else { + closing = str.indexOf(quote + ']', bracket); + if (closing === -1) { + closing = str.length; + } - if (dot === -1 && bracket === -1) { - // we've reached the end - parts.push(str.slice(i, str.length)); - i = str.length; - } else if (bracket === -1 || (dot !== -1 && dot < bracket)) { - // dots - parts.push(str.slice(i, dot)); - i = dot + 1; - } else { - // brackets - if (bracket > i) { - parts.push(str.slice(i, bracket)); - i = bracket; - } - quote = str.slice(bracket + 1, bracket + 2); - - if (quote !== '"' && quote !== "'") { - closing = str.indexOf(']', bracket); - if (closing === -1) { - closing = str.length; - } - parts.push(str.slice(i + 1, closing)); - i = (str.slice(closing + 1, closing + 2) === '.') ? closing + 2 : closing + 1; - } else { - closing = str.indexOf(quote + ']', bracket); - if (closing === -1) { - closing = str.length; - } - - /* + /* When there is an even number of backslashes before a single quote, the single quote is not actually escaped. The original implementation of the library did not account for this situation, causing the program to enter an infinite loop when the key ends with a backslash. */ - while ( - closing !== -1 && - (function countBackslashesBeforeClosing() { - let backslash = 0; - while (str.slice(closing - 1 - backslash, closing - backslash) === '\\') { - backslash++; - } - return backslash; - })() % 2 === 1 && - bracket < str.length - ) { - bracket++; - closing = str.indexOf(quote + ']', bracket); - } - if (closing === -1) { - closing = str.length; - } + while ( + closing !== -1 && + (function countBackslashesBeforeClosing() { + let backslash = 0; + while (str.slice(closing - 1 - backslash, closing - backslash) === '\\') { + backslash++; + } + return backslash; + })() % + 2 === + 1 && + bracket < str.length + ) { + bracket++; + closing = str.indexOf(quote + ']', bracket); + } + if (closing === -1) { + closing = str.length; + } - parts.push(str.slice(i + 2, closing).replace(regex[quote], quote).replace(/\\+/g, function (backslash) { - return new Array(Math.ceil(backslash.length / 2) + 1).join('\\'); - })); - i = (str.slice(closing + 2, closing + 3) === '.') ? closing + 3 : closing + 2; - } - } + parts.push( + str + .slice(i + 2, closing) + .replace(regex[quote], quote) + .replace(/\\+/g, function (backslash) { + return new Array(Math.ceil(backslash.length / 2) + 1).join('\\'); + }), + ); + i = str.slice(closing + 2, closing + 3) === '.' ? closing + 3 : closing + 2; } - return parts; - }, + } + } + return parts; + }, - // root === true : auto calculate root; must be dot-notation friendly - // root String : the string to use as root - stringify: function (arr: (string | number)[], quote?: Quote, forceQuote?: boolean) { - if (!Array.isArray(arr)) { - arr = [arr]; + // root === true : auto calculate root; must be dot-notation friendly + // root String : the string to use as root + stringify: function (arr: (string | number)[], quote?: Quote, forceQuote?: boolean) { + if (!Array.isArray(arr)) { + arr = [arr]; + } + + quote = quote === '"' ? '"' : "'"; + const regexp = new RegExp('(\\\\|' + quote + ')', 'g'); // regex => /(\\|')/g + + return arr + .map(function (value: string | number, key: number) { + let property = value.toString(); + if (!forceQuote && /^[A-z_]\w*$/.exec(property)) { + // str with only A-z0-9_ chars will display `foo.bar` + return key !== 0 ? '.' + property : property; + } else if (!forceQuote && /^\d+$/.exec(property)) { + // str with only numbers will display `foo[0]` + return '[' + property + ']'; } + property = property.replace(regexp, '\\$1'); + return '[' + quote + property + quote + ']'; + }) + .join(''); + }, - quote = (quote === '"') ? '"' : "'"; - const regexp = new RegExp('(\\\\|' + quote + ')', 'g'); // regex => /(\\|')/g - - return arr.map(function (value: string | number, key: number) { - let property = value.toString(); - if (!forceQuote && /^[A-z_]\w*$/.exec(property)) { // str with only A-z0-9_ chars will display `foo.bar` - return (key !== 0) ? '.' + property : property; - } else if (!forceQuote && /^\d+$/.exec(property)) { // str with only numbers will display `foo[0]` - return '[' + property + ']'; - } - property = property.replace(regexp, '\\$1'); - return '[' + quote + property + quote + ']'; - - }).join(''); - }, - - normalize: function (data: string, quote?: Quote, forceQuote?: boolean) { - return ObjectPath.stringify(Array.isArray(data) ? data : ObjectPath.parse(data), quote, forceQuote); - }, + normalize: function (data: string, quote?: Quote, forceQuote?: boolean) { + return ObjectPath.stringify(Array.isArray(data) ? data : ObjectPath.parse(data), quote, forceQuote); + }, }; export default ObjectPath; diff --git a/packages/insomnia/src/templating/types.ts b/packages/insomnia/src/templating/types.ts index ea6e0f2649..9e6e2dd7cb 100644 --- a/packages/insomnia/src/templating/types.ts +++ b/packages/insomnia/src/templating/types.ts @@ -48,9 +48,10 @@ export interface BaseRenderContextOptions { } export type RenderContextAncestor = Request | GrpcRequest | WebSocketRequest | RequestGroup | Workspace | Project; -export type RenderContextOptions = BaseRenderContextOptions - & Partial - & { ancestors?: RenderContextAncestor[] }; +export type RenderContextOptions = BaseRenderContextOptions & + Partial & { + ancestors?: RenderContextAncestor[]; + }; export type NunjucksTagContextMenuAction = 'edit' | 'delete'; @@ -164,8 +165,15 @@ export interface BaseRenderContext { } export interface AppContext { alert: (title: string, message?: string) => void; - dialog: (title: string, body: HTMLElement, options?: { onHide?: () => void; tall?: boolean; skinny?: boolean; wide?: boolean }) => void; - prompt: (title: string, options?: Pick) => Promise; + dialog: ( + title: string, + body: HTMLElement, + options?: { onHide?: () => void; tall?: boolean; skinny?: boolean; wide?: boolean }, + ) => void; + prompt: ( + title: string, + options?: Pick, + ) => Promise; getPath: (name: string) => string; getInfo: () => { version: string; platform: NodeJS.Platform }; showSaveDialog: (options?: { defaultPath?: string }) => Promise; diff --git a/packages/insomnia/src/templating/utils.ts b/packages/insomnia/src/templating/utils.ts index 829d12264a..0a38afaea1 100644 --- a/packages/insomnia/src/templating/utils.ts +++ b/packages/insomnia/src/templating/utils.ts @@ -12,10 +12,7 @@ import objectPath from './third_party/objectPath'; * @param {String} [prefix] - base path to prefix to all paths * @returns {Array} - list of paths */ -export function getKeys( - obj: any, - prefix = '', -): { name: string; value: any }[] { +export function getKeys(obj: any, prefix = ''): { name: string; value: any }[] { let allKeys: { name: string; value: any }[] = []; const typeOfObj = Object.prototype.toString.call(obj); @@ -237,18 +234,18 @@ export function decodeEncoding(value: T) { export async function maskOrDecryptVaultDataIfNecessary(vaultEnvironmentData: any, renderPurpose?: RenderPurpose) { /** - * Decrypt secrets when renderPurpose is one of the following: - * - preview: render the template in variable editor to do the live preview - * - send: render the template when sending requests - * - script: render the template in pre-request or after-response script - */ + * Decrypt secrets when renderPurpose is one of the following: + * - preview: render the template in variable editor to do the live preview + * - send: render the template when sending requests + * - script: render the template in pre-request or after-response script + */ const shouldDecrypt = renderPurpose === 'preview' || renderPurpose === 'send' || renderPurpose === 'script'; if (typeof vaultEnvironmentData === 'object') { if (shouldDecrypt) { const { vaultKey, vaultSalt } = await userSession.getOrCreate(); const isVaultEnabled = !!vaultSalt; if (isVaultEnabled && vaultKey) { - const symmetricKey = await decryptVaultKeyFromSession(vaultKey, true) as JsonWebKey; + const symmetricKey = (await decryptVaultKeyFromSession(vaultKey, true)) as JsonWebKey; // decrypt all secert values under vaultEnvironmentPath property in context Object.keys(vaultEnvironmentData).forEach(vaultContextKey => { const encryptedValue = vaultEnvironmentData[vaultContextKey]; @@ -270,7 +267,7 @@ export async function maskOrDecryptVaultDataIfNecessary(vaultEnvironmentData: an export function extractNunjucksTagFromCoords( coordinates: { left: number; top: number }, - cm: React.MutableRefObject + cm: React.MutableRefObject, ): { range: MarkerRange; template: string } | void { if (cm && cm.current) { const { left, top } = coordinates; diff --git a/packages/insomnia/src/templating/worker.ts b/packages/insomnia/src/templating/worker.ts index 093e37b817..24364c3957 100644 --- a/packages/insomnia/src/templating/worker.ts +++ b/packages/insomnia/src/templating/worker.ts @@ -1,4 +1,3 @@ - import type { Environment } from 'nunjucks'; import nunjucks from 'nunjucks/browser/nunjucks'; @@ -69,9 +68,7 @@ export function render( const location = err.message.match(/\[Line (\d+), Column (\d+)*]/); const line = location ? parseInt(location[1]) : 1; const column = location ? parseInt(location[2]) : 1; - const reason = err.message.includes('attempted to output null or undefined value') - ? 'undefined' - : 'error'; + const reason = err.message.includes('attempted to output null or undefined value') ? 'undefined' : 'error'; const newError = new RenderError(sanitizedMsg); newError.path = path || ''; newError.message = sanitizedMsg; @@ -174,9 +171,7 @@ async function getNunjucks(renderMode: string, ignoreUndefinedEnvVariable?: bool // ~~~~~~~~~~~~~~~~~~~~~~~~~~ // const nunjucksEnvironment = nunjucks.configure(config) as NunjucksEnvironment; - const allExtensions = [ - ...localTemplateTags, - ]; + const allExtensions = [...localTemplateTags]; for (const extension of allExtensions) { const { templateTag, plugin } = extension; diff --git a/packages/insomnia/src/ui/analytics.ts b/packages/insomnia/src/ui/analytics.ts index f5688a6b5b..95b0a8b905 100644 --- a/packages/insomnia/src/ui/analytics.ts +++ b/packages/insomnia/src/ui/analytics.ts @@ -36,14 +36,23 @@ export enum SegmentEvent { } type PushPull = 'push' | 'pull'; -type VCSAction = PushPull | `force_${PushPull}` | - 'create_branch' | 'merge_branch' | 'delete_branch' | 'checkout_branch' | - 'commit' | 'stage_all' | 'stage' | 'unstage_all' | 'unstage' | 'rollback' | 'rollback_all' | - 'update' | 'setup' | 'clone'; -export function vcsSegmentEventProperties( - type: 'git', - action: VCSAction, - error?: string -) { +type VCSAction = + | PushPull + | `force_${PushPull}` + | 'create_branch' + | 'merge_branch' + | 'delete_branch' + | 'checkout_branch' + | 'commit' + | 'stage_all' + | 'stage' + | 'unstage_all' + | 'unstage' + | 'rollback' + | 'rollback_all' + | 'update' + | 'setup' + | 'clone'; +export function vcsSegmentEventProperties(type: 'git', action: VCSAction, error?: string) { return { type, action, error }; } diff --git a/packages/insomnia/src/ui/auth-session-provider.ts b/packages/insomnia/src/ui/auth-session-provider.ts index 2312d7cc6f..de6941c7c9 100644 --- a/packages/insomnia/src/ui/auth-session-provider.ts +++ b/packages/insomnia/src/ui/auth-session-provider.ts @@ -36,7 +36,6 @@ export async function decodeBase64(base64: string): Promise { const res = await fetch(uri); const buffer = await res.arrayBuffer(); return new Uint8Array(buffer); - } catch (error) { console.error(error); throw new Error('Failed to decode base64'); diff --git a/packages/insomnia/src/ui/components/app-loading-indicator.tsx b/packages/insomnia/src/ui/components/app-loading-indicator.tsx index f1f9a5c224..01f4e20e96 100644 --- a/packages/insomnia/src/ui/components/app-loading-indicator.tsx +++ b/packages/insomnia/src/ui/components/app-loading-indicator.tsx @@ -16,13 +16,7 @@ export const AppLoadingIndicator = () => ( position: 'relative', }} > - + ( transform="translate(-323 -111) translate(359.016 147.016) scale(4.24956)" > >(props => ( - + diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnBrackets.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnBrackets.tsx index 29801043b0..d01ae12d1a 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnBrackets.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnBrackets.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnBrackets = memo>(props => ( - + >(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnChevronDown.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnChevronDown.tsx index e87c61c20a..e60d888fb3 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnChevronDown.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnChevronDown.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnChevronDown = memo>(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnChevronUp.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnChevronUp.tsx index d3897348bb..e1b8dd7c81 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnChevronUp.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnChevronUp.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnChevronUp = memo>(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnElevator.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnElevator.tsx index e3f347442f..dd8ab9bbed 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnElevator.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnElevator.tsx @@ -10,11 +10,6 @@ export const SvgIcnElevator = memo>(props => ( {...props} > - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnEllipsis.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnEllipsis.tsx index 68826f8e96..9d97ebb05c 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnEllipsis.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnEllipsis.tsx @@ -1,14 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnEllipsis = memo>(props => ( - + >(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnErrors.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnErrors.tsx index c1e2de5093..1e8c3dcf76 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnErrors.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnErrors.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnErrors = memo>(props => ( - + >(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnGitBranch.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnGitBranch.tsx index 3347f20feb..41b93a5276 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnGitBranch.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnGitBranch.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnGitBranch = memo>(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnGithubLogo.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnGithubLogo.tsx index cd4e4fa273..a3225b941d 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnGithubLogo.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnGithubLogo.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnGithubLogo = memo>(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnInfo.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnInfo.tsx index 041b6b66d6..9bce89acf9 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnInfo.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnInfo.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnInfo = memo>(props => ( - + >(props => ( - + >(props => ( - + )); diff --git a/packages/insomnia/src/ui/components/assets/svgr/IcnSuccess.tsx b/packages/insomnia/src/ui/components/assets/svgr/IcnSuccess.tsx index 1a8b79f39f..9b38d84adb 100644 --- a/packages/insomnia/src/ui/components/assets/svgr/IcnSuccess.tsx +++ b/packages/insomnia/src/ui/components/assets/svgr/IcnSuccess.tsx @@ -1,13 +1,6 @@ import React, { memo, type SVGProps } from 'react'; export const SvgIcnSuccess = memo>(props => ( - + >(props => ( - + ); }; @@ -39,7 +41,7 @@ const AvatarImage = ({ src, alt, size }: { src: string; alt: string; size: 'smal const AvatarPlaceholder = ({ size, children }: { size: 'small' | 'medium'; children: ReactNode }) => { return (
{children}
@@ -49,27 +51,29 @@ const AvatarPlaceholder = ({ size, children }: { size: 'small' | 'medium'; child export const Avatar = ({ src, alt, size = 'medium' }: { src: string; alt: string; size?: 'small' | 'medium' }) => { return src ? ( {getNameInitials(alt)}}> - + ) : ( - - {getNameInitials(alt)} - + {getNameInitials(alt)} ); }; -export const AvatarGroup = ({ items, maxAvatars = 3, size = 'medium' }: { items: { key: string; src: string; alt: string }[]; maxAvatars?: number; size: 'small' | 'medium' }) => { +export const AvatarGroup = ({ + items, + maxAvatars = 3, + size = 'medium', +}: { + items: { key: string; src: string; alt: string }[]; + maxAvatars?: number; + size: 'small' | 'medium'; +}) => { const avatars = items.slice(0, maxAvatars); const overflow = items.length - maxAvatars; return ( }>
( {avatar.alt} @@ -98,7 +98,7 @@ export const AvatarGroup = ({ items, maxAvatars = 3, size = 'medium' }: { items: {items.slice(maxAvatars).map(avatar => (
{avatar.alt}
diff --git a/packages/insomnia/src/ui/components/base/copy-button.tsx b/packages/insomnia/src/ui/components/base/copy-button.tsx index 4717f03fc0..6112d1efbd 100644 --- a/packages/insomnia/src/ui/components/base/copy-button.tsx +++ b/packages/insomnia/src/ui/components/base/copy-button.tsx @@ -19,15 +19,18 @@ export const CopyButton: FC = ({ ...buttonProps }) => { const [showConfirmation, setShowConfirmation] = useState(false); - const onClick = useCallback(async (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); + const onClick = useCallback( + async (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); - if (content) { - window.clipboard.writeText(content); - } - setShowConfirmation(true); - }, [content]); + if (content) { + window.clipboard.writeText(content); + } + setShowConfirmation(true); + }, + [content], + ); useInterval(() => { setShowConfirmation(false); @@ -35,12 +38,8 @@ export const CopyButton: FC = ({ const confirm = typeof confirmMessage === 'string' ? confirmMessage : 'Copied'; return ( - } + {triggerButton || ( + + )} {state.isOpen && ( - + = (props: ItemContentProps) => { const content = ( <> -
- {icon && typeof icon === 'string' ? : icon} +
+ {icon && typeof icon === 'string' ? ( + + ) : ( + icon + )} {children || label}
{hint && } @@ -40,7 +41,7 @@ export const ItemContent: FC = (props: ItemContentProps) => { return ( {content} @@ -50,8 +51,8 @@ export const ItemContent: FC = (props: ItemContentProps) => { return (
{content} diff --git a/packages/insomnia/src/ui/components/base/dropdown/menu-item.tsx b/packages/insomnia/src/ui/components/base/dropdown/menu-item.tsx index 7036d7496c..56137ce9c3 100644 --- a/packages/insomnia/src/ui/components/base/dropdown/menu-item.tsx +++ b/packages/insomnia/src/ui/components/base/dropdown/menu-item.tsx @@ -10,11 +10,7 @@ interface Props { closeOnSelect?: boolean; } -export const MenuItem = ({ - item, - state, - closeOnSelect, -}: Props) => { +export const MenuItem = ({ item, state, closeOnSelect }: Props) => { const ref = useRef(null); /** @@ -36,19 +32,20 @@ export const MenuItem = ({ // of if the dropdown is not set to close on select. const shouldRemainOpen = withPrompt || stayOpenAfterClick; - const { - menuItemProps, - isFocused, - } = useMenuItem({ - key: item.key, - 'aria-label': item['aria-label'], - closeOnSelect: closeOnSelect || !shouldRemainOpen, - onAction: !withPrompt && !isDisabled && onClick, - }, state, ref); + const { menuItemProps, isFocused } = useMenuItem( + { + 'key': item.key, + 'aria-label': item['aria-label'], + 'closeOnSelect': closeOnSelect || !shouldRemainOpen, + 'onAction': !withPrompt && !isDisabled && onClick, + }, + state, + ref, + ); return (
  • diff --git a/packages/insomnia/src/ui/components/base/dropdown/menu-section.tsx b/packages/insomnia/src/ui/components/base/dropdown/menu-section.tsx index 1c85ee1420..64d3ce9a03 100644 --- a/packages/insomnia/src/ui/components/base/dropdown/menu-section.tsx +++ b/packages/insomnia/src/ui/components/base/dropdown/menu-section.tsx @@ -11,13 +11,9 @@ interface Props { closeOnSelect?: boolean; } -export const MenuSection = ({ - section, - state, - closeOnSelect = true, -}: Props) => { +export const MenuSection = ({ section, state, closeOnSelect = true }: Props) => { const { itemProps, headingProps, groupProps } = useMenuSection({ - heading: section.rendered, + 'heading': section.rendered, 'aria-label': section['aria-label'], }); @@ -27,28 +23,19 @@ export const MenuSection = ({ return (
  • -
    +
    {section.rendered && ( - + {section.rendered} )} {shouldDisplayDivider &&
    }
    -
      - {[...section.childNodes].map((node: Node) => ( - node.rendered && ( - - ) - ))} +
        + {[...section.childNodes].map( + (node: Node) => + node.rendered && , + )}
      ); diff --git a/packages/insomnia/src/ui/components/base/dropdown/menu.tsx b/packages/insomnia/src/ui/components/base/dropdown/menu.tsx index e95a9570a0..e7468bacda 100644 --- a/packages/insomnia/src/ui/components/base/dropdown/menu.tsx +++ b/packages/insomnia/src/ui/components/base/dropdown/menu.tsx @@ -42,26 +42,12 @@ export const Menu = (props: Props) => { // If the item is a section and the section has items, render a MenuSection // @ts-expect-error -- early deprecation if (item.type === 'section' && getItemCount(item.childNodes) !== 0) { - return ( - - ); + return ; } // If the item is a dropdown item and has content, render a MenuItem if (item.type === 'item' && item.rendered) { - return ( - - ); + return ; } return null; diff --git a/packages/insomnia/src/ui/components/base/dropdown/popover.tsx b/packages/insomnia/src/ui/components/base/dropdown/popover.tsx index f22043188b..18cd23d372 100644 --- a/packages/insomnia/src/ui/components/base/dropdown/popover.tsx +++ b/packages/insomnia/src/ui/components/base/dropdown/popover.tsx @@ -11,11 +11,7 @@ export const Popover: FC = (props: Props) => { const popoverRef = useRef(null); const { state, children, isNonModal } = props; - const { overlayProps } = useOverlayTrigger( - { type: 'dialog' }, - state, - props.triggerRef - ); + const { overlayProps } = useOverlayTrigger({ type: 'dialog' }, state, props.triggerRef); // Handle events that should cause the popup to close, // e.g. blur, clicking outside, or pressing the escape key. diff --git a/packages/insomnia/src/ui/components/base/file-input-button.tsx b/packages/insomnia/src/ui/components/base/file-input-button.tsx index 2c60bd19a4..de77abc7b4 100644 --- a/packages/insomnia/src/ui/components/base/file-input-button.tsx +++ b/packages/insomnia/src/ui/components/base/file-input-button.tsx @@ -18,7 +18,7 @@ export const FileInputButton = (props: Props) => { const { showFileName, showFileIcon, path, name, onChange, itemtypes, extensions, disabled, ...extraProps } = props; // NOTE: Basename fails if path is not a string, so let's make sure it is const fileName = typeof path === 'string' ? pathBasename(path) : null; - const _handleChooseFile = useCallback(async () => { + const _handleChooseFile = useCallback(async () => { const { canceled, filePath } = await selectFileOrFolder({ itemTypes: itemtypes, extensions, @@ -32,13 +32,7 @@ export const FileInputButton = (props: Props) => { onChange(filePath); }, [extensions, itemtypes, onChange]); return ( - diff --git a/packages/insomnia/src/ui/components/base/highlight.tsx b/packages/insomnia/src/ui/components/base/highlight.tsx index edf38fbb06..a7fafd73ab 100644 --- a/packages/insomnia/src/ui/components/base/highlight.tsx +++ b/packages/insomnia/src/ui/components/base/highlight.tsx @@ -9,12 +9,7 @@ export interface HighlightProps { blankValue?: string; } -export const Highlight: FC = ({ - search, - text, - blankValue, - ...otherProps -}) => { +export const Highlight: FC = ({ search, text, blankValue, ...otherProps }) => { // Match loose here to make sure our highlighting always works const result = fuzzyMatch(search, text, { splitSpace: true, @@ -29,12 +24,12 @@ export const Highlight: FC = ({ ', - '' + '', ), }} /> diff --git a/packages/insomnia/src/ui/components/base/indeterminate-checkbox.tsx b/packages/insomnia/src/ui/components/base/indeterminate-checkbox.tsx index 4affeebf3c..57937649a1 100644 --- a/packages/insomnia/src/ui/components/base/indeterminate-checkbox.tsx +++ b/packages/insomnia/src/ui/components/base/indeterminate-checkbox.tsx @@ -15,11 +15,5 @@ export const IndeterminateCheckbox: FC = ({ checked, indeterminate, ...ot } }, [checked, indeterminate]); - return ( - - ); + return ; }; diff --git a/packages/insomnia/src/ui/components/base/link.tsx b/packages/insomnia/src/ui/components/base/link.tsx index 71ad8db22c..79965a6847 100644 --- a/packages/insomnia/src/ui/components/base/link.tsx +++ b/packages/insomnia/src/ui/components/base/link.tsx @@ -11,20 +11,15 @@ interface Props { noTheme?: boolean; } -export const Link: FC = ({ - onClick, - button, - href, - children, - className, - noTheme, - ...other -}) => { - const handleClick = useCallback((event: React.MouseEvent) => { - event?.preventDefault(); - onClick?.(event); // Also call onClick that was passed to us if there was one - window.main.openInBrowser(href); - }, [onClick, href]); +export const Link: FC = ({ onClick, button, href, children, className, noTheme, ...other }) => { + const handleClick = useCallback( + (event: React.MouseEvent) => { + event?.preventDefault(); + onClick?.(event); // Also call onClick that was passed to us if there was one + window.main.openInBrowser(href); + }, + [onClick, href], + ); if (button) { return ( diff --git a/packages/insomnia/src/ui/components/base/modal-footer.tsx b/packages/insomnia/src/ui/components/base/modal-footer.tsx index f4c0a40f3a..36f5c0db36 100644 --- a/packages/insomnia/src/ui/components/base/modal-footer.tsx +++ b/packages/insomnia/src/ui/components/base/modal-footer.tsx @@ -7,9 +7,7 @@ interface Props { } export const ModalFooter: FC = memo(({ children, className }) => ( -
      - {children} -
      +
      {children}
      )); ModalFooter.displayName = 'ModalFooter'; diff --git a/packages/insomnia/src/ui/components/base/modal.tsx b/packages/insomnia/src/ui/components/base/modal.tsx index bb13d864e4..46a0d2f998 100644 --- a/packages/insomnia/src/ui/components/base/modal.tsx +++ b/packages/insomnia/src/ui/components/base/modal.tsx @@ -1,5 +1,13 @@ import classnames from 'classnames'; -import React, { forwardRef, type ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import React, { + forwardRef, + type ReactNode, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react'; import { FocusScope } from 'react-aria'; import { createKeybindingsHandler } from '../keydown-binder'; @@ -24,95 +32,92 @@ export interface ModalHandle { toggle: () => void; isOpen: () => boolean; } -export const Modal = forwardRef(({ - centered, - children, - className, - onHide: onHideProp, - onShow, - skinny, - tall, - wide, -}, ref) => { - const containerRef = useRef(null); - const [open, setOpen] = useState(false); - const [zIndex, setZIndex] = useState(globalZIndex); - const [onHideArgument, setOnHideArgument] = useState<() => void>(); +export const Modal = forwardRef( + ({ centered, children, className, onHide: onHideProp, onShow, skinny, tall, wide }, ref) => { + const containerRef = useRef(null); + const [open, setOpen] = useState(false); + const [zIndex, setZIndex] = useState(globalZIndex); + const [onHideArgument, setOnHideArgument] = useState<() => void>(); - const show: ModalHandle['show'] = useCallback(options => { - options?.onHide && setOnHideArgument(options.onHide); - setOpen(true); - setZIndex(globalZIndex++); - onShow?.(); - }, [onShow]); + const show: ModalHandle['show'] = useCallback( + options => { + options?.onHide && setOnHideArgument(options.onHide); + setOpen(true); + setZIndex(globalZIndex++); + onShow?.(); + }, + [onShow], + ); - const hide = useCallback(() => { - setOpen(false); - if (typeof onHideProp === 'function') { - onHideProp(); - } - if (typeof onHideArgument === 'function') { - onHideArgument(); - } - }, [onHideProp, onHideArgument]); + const hide = useCallback(() => { + setOpen(false); + if (typeof onHideProp === 'function') { + onHideProp(); + } + if (typeof onHideArgument === 'function') { + onHideArgument(); + } + }, [onHideProp, onHideArgument]); - useImperativeHandle(ref, () => ({ - show, - hide, - toggle: () => open ? hide() : show(), - isOpen: () => open, - }), [show, open, hide]); + useImperativeHandle( + ref, + () => ({ + show, + hide, + toggle: () => (open ? hide() : show()), + isOpen: () => open, + }), + [show, open, hide], + ); - const classes = classnames( - 'modal', - 'theme--dialog', - className, - { 'modal--fixed-height': tall }, - { 'modal--wide': wide }, - { 'modal--skinny': skinny }, - ); + const classes = classnames( + 'modal', + 'theme--dialog', + className, + { 'modal--fixed-height': tall }, + { 'modal--wide': wide }, + { 'modal--skinny': skinny }, + ); - useEffect(() => { - const closeElements = containerRef.current?.querySelectorAll('[data-close-modal]'); + useEffect(() => { + const closeElements = containerRef.current?.querySelectorAll('[data-close-modal]'); - for (const element of closeElements || []) { - element.addEventListener('click', hide); - } - }, [hide, open]); + for (const element of closeElements || []) { + element.addEventListener('click', hide); + } + }, [hide, open]); - const handleKeydown = createKeybindingsHandler({ - 'Escape': () => { - hide(); - }, - }); - useEffect(() => { - document.body.addEventListener('keydown', handleKeydown); + const handleKeydown = createKeybindingsHandler({ + Escape: () => { + hide(); + }, + }); + useEffect(() => { + document.body.addEventListener('keydown', handleKeydown); - return () => { - document.body.removeEventListener('keydown', handleKeydown); - }; - }, [handleKeydown]); + return () => { + document.body.removeEventListener('keydown', handleKeydown); + }; + }, [handleKeydown]); - return (open ? - -
      -
      -
      -
      - {children} + return open ? ( + +
      +
      +
      +
      {children}
      -
      -
      - : null - ); -}); + + ) : null; + }, +); Modal.displayName = 'Modal'; diff --git a/packages/insomnia/src/ui/components/base/prompt-button.tsx b/packages/insomnia/src/ui/components/base/prompt-button.tsx index e88553f6a8..b4cccec7d6 100644 --- a/packages/insomnia/src/ui/components/base/prompt-button.tsx +++ b/packages/insomnia/src/ui/components/base/prompt-button.tsx @@ -24,7 +24,7 @@ interface Props { referToOnClickReturnValue?: boolean; } -export const PromptButton = ({ +export const PromptButton = ({ onClick, disabled, confirmMessage = 'Click to confirm', @@ -86,13 +86,15 @@ export const PromptButton = ({ } else { if (retVal instanceof Promise) { setState('loading'); - retVal.then(() => { - setState('done'); - }).finally(() => { - triggerTimeout.current = global.setTimeout(() => { - setState('default'); - }, 1000); - }); + retVal + .then(() => { + setState('done'); + }) + .finally(() => { + triggerTimeout.current = global.setTimeout(() => { + setState('default'); + }, 1000); + }); } else { throw new Error('onClick must return a Promise when referToOnClickReturnValue is true'); } @@ -131,30 +133,33 @@ interface PromptMessageProps { loadingMessage: string; children: ReactNode; } -const PromptMessage: FunctionComponent = ({ promptState, confirmMessage, doneMessage, loadingMessage, children }) => { - +const PromptMessage: FunctionComponent = ({ + promptState, + confirmMessage, + doneMessage, + loadingMessage, + children, +}) => { if (promptState === 'ask') { return ( - - - {confirmMessage && ( - {confirmMessage} - )} + + + {confirmMessage && {confirmMessage}} ); } if (promptState === 'loading') { return ( - - - {loadingMessage} + + + {loadingMessage} ); } if (promptState === 'done' && doneMessage) { - return {doneMessage}; + return {doneMessage}; } return <>{children}; diff --git a/packages/insomnia/src/ui/components/buttons/grpc-send-button.tsx b/packages/insomnia/src/ui/components/buttons/grpc-send-button.tsx index a4501116f3..5eeb5aa37c 100644 --- a/packages/insomnia/src/ui/components/buttons/grpc-send-button.tsx +++ b/packages/insomnia/src/ui/components/buttons/grpc-send-button.tsx @@ -13,10 +13,7 @@ interface Props { export const GrpcSendButton: FunctionComponent = ({ running, methodType, handleStart, handleCancel }) => { if (!methodType) { return ( - ); @@ -24,7 +21,7 @@ export const GrpcSendButton: FunctionComponent = ({ running, methodType, return (