diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index dc4d85bc51..0000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,56 +0,0 @@ -image: Visual Studio 2015 -build: off -test: off - -#---------------------------------# -# environment configuration # -#---------------------------------# - -shallow_clone: true -platform: x64 -environment: - NODEJS_VERSION: '10' - CSC_LINK: '%WINDOWS_CSC_LINK%' - CSC_KEY_PASSWORD: '%WINDOWS_CSC_KEY_PASSWORD%' - -# Things to install after repo clone -install: - - SET "PATH=%PATH%;C:\Program Files\Git\mingw64\libexec\git-core" # For weird git bug - - ps: Install-Product node $env:NODEJS_VERSION $env:Platform - - node --version - - npm --version - - npm config set msvs_version 2015 - - npm run bootstrap - - npm test - -cache: - - '%USERPROFILE%\.electron -> packages/insomnia-app/package.json' - -#---------------------------------# -# tests configuration # -#---------------------------------# - -build_script: - - if %APPVEYOR_REPO_TAG%==true npm run app-package - -#---------------------------------# -# artifacts configuration # -#---------------------------------# - -artifacts: - - path: packages\insomnia-app\dist\squirrel-windows\* - name: dist - -#---------------------------------# -# deployment configuration # -#---------------------------------# - -deploy: - description: '' - provider: GitHub - auth_token: - secure: Ffmgxn+wt5WSf/jgJ/L+/3mkUs4fn9Z5j4Dz73VATsgL14Rf/xUp2nOyE0ecow+1 - artifact: dist - prerelease: true - on: - appveyor_repo_tag: true diff --git a/.eslintrc b/.eslintrc.json similarity index 72% rename from .eslintrc rename to .eslintrc.json index 518e4c3bcd..9347b8bf44 100644 --- a/.eslintrc +++ b/.eslintrc.json @@ -1,17 +1,7 @@ { "parser": "babel-eslint", - "extends": [ - "semistandard", - "plugin:flowtype/recommended" - ], - "plugins": [ - "react", - "jest", - "html", - "json", - "filenames", - "flowtype" - ], + "extends": ["semistandard", "plugin:flowtype/recommended"], + "plugins": ["react", "jest", "html", "json", "filenames", "flowtype"], "parserOptions": { "ecmaFeatures": { "jsx": true @@ -29,10 +19,7 @@ "jest/globals": true }, "rules": { - "comma-dangle": [ - "error", - "always-multiline" - ], + "comma-dangle": ["error", "always-multiline"], "indent": "off", "no-var": "error", "no-duplicate-imports": "off", @@ -48,10 +35,7 @@ "asyncArrow": "always" } ], - "filenames/match-exported": [ - "error", - "kebab" - ] + "filenames/match-exported": ["error", "kebab"] }, "settings": { "flowtype": { diff --git a/.github/actions/build-linux/Dockerfile b/.github/actions/build-linux/Dockerfile new file mode 100644 index 0000000000..6e385f59be --- /dev/null +++ b/.github/actions/build-linux/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:14.04 + +LABEL "name"="Insomnia-Ubuntu-14" +LABEL "maintainer"="Gregory Schier " +LABEL "version"="1.0.0" + +LABEL "com.github.actions.icon"="package" +LABEL "com.github.actions.color"="blue" +LABEL "com.github.actions.name"="Insomnia Ubuntu 14" +LABEL "com.github.actions.description"="Do the stuff" + +COPY entrypoint.sh /scripts/entrypoint.sh +COPY install-dependencies.sh /scripts/install-dependencies.sh + +RUN chmod +x /scripts/* +RUN /scripts/install-dependencies.sh + +ENTRYPOINT ["/scripts/entrypoint.sh"] \ No newline at end of file diff --git a/.github/actions/build-linux/entrypoint.sh b/.github/actions/build-linux/entrypoint.sh new file mode 100644 index 0000000000..2802e0a5e8 --- /dev/null +++ b/.github/actions/build-linux/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Fail on any errors +set -e + +if [ -z "$GITHUB_WORKSPACE" ]; then + echo "Set the GITHUB_WORKSPACE env variable." + exit 1 +fi + +# Install root project dependencies +cd "$GITHUB_WORKSPACE" +npm run bootstrap +npm install --no-save 7zip-bin-linux app-builder-bin-linux + +echo "Running the stuff" +npm test +npm run app-release \ No newline at end of file diff --git a/docker/install-dependencies.sh b/.github/actions/build-linux/install-dependencies.sh old mode 100755 new mode 100644 similarity index 98% rename from docker/install-dependencies.sh rename to .github/actions/build-linux/install-dependencies.sh index ea30270092..f6c83fac85 --- a/docker/install-dependencies.sh +++ b/.github/actions/build-linux/install-dependencies.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # Fail on any errors set -e diff --git a/.github/actions/build-snap/Dockerfile b/.github/actions/build-snap/Dockerfile new file mode 100644 index 0000000000..6fe84268e9 --- /dev/null +++ b/.github/actions/build-snap/Dockerfile @@ -0,0 +1,19 @@ +FROM snapcore/snapcraft:stable + +LABEL "name"="Insomnia-Ubuntu-16" +LABEL "maintainer"="Gregory Schier " +LABEL "version"="1.0.0" + +LABEL "com.github.actions.icon"="package" +LABEL "com.github.actions.color"="blue" +LABEL "com.github.actions.name"="Insomnia Ubuntu 16" +LABEL "com.github.actions.description"="Do the stuff" + +COPY entrypoint.sh /scripts/entrypoint.sh +COPY install-dependencies.sh /scripts/install-dependencies.sh + +RUN chmod +x /scripts/* +RUN /scripts/install-dependencies.sh +RUN snapcraft --version + +ENTRYPOINT ["/scripts/entrypoint.sh"] \ No newline at end of file diff --git a/.github/actions/build-snap/entrypoint.sh b/.github/actions/build-snap/entrypoint.sh new file mode 100644 index 0000000000..d247690309 --- /dev/null +++ b/.github/actions/build-snap/entrypoint.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# Fail on any errors +set -e + +if [ -z "$GITHUB_WORKSPACE" ]; then + echo "Set the GITHUB_WORKSPACE env variable." + exit 1 +fi + +# Install root project dependencies +cd "$GITHUB_WORKSPACE" +npm run bootstrap +npm install --no-save 7zip-bin-linux app-builder-bin-linux + +echo "Running the stuff" +npm test + +# Log into snapcraft for publishing +echo "$SNAPCRAFT_LOGIN_FILE" > snapcraft.txt && snapcraft login --with snapcraft.txt + +npm run app-release \ No newline at end of file diff --git a/.github/actions/build-snap/install-dependencies.sh b/.github/actions/build-snap/install-dependencies.sh new file mode 100644 index 0000000000..f6c83fac85 --- /dev/null +++ b/.github/actions/build-snap/install-dependencies.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +# Fail on any errors +set -e + +# Install core deps +apt-get update +apt-get upgrade -y +apt-get install -y \ + build-essential \ + autoconf \ + libtool \ + pkg-config \ + libfontconfig1-dev \ + rpm \ + wget + +# Install Node and app-related dependencies +wget -O- https://deb.nodesource.com/setup_10.x | bash - +apt-get install -y nodejs graphicsmagick icnsutils + +# Build zlib from source (for Curl) +wget -q https://github.com/madler/zlib/archive/v1.2.11.tar.gz -O ./zlib.tar.gz +mkdir -p /src/zlib /build/zlib +tar -xf zlib.tar.gz -C /src/zlib --strip 1 +cd /src/zlib +./configure --prefix=/build/zlib +make +make install +ldconfig + +# Build OpenSSL from source (for Curl) +wget -q https://github.com/openssl/openssl/archive/OpenSSL_1_1_0h.tar.gz -O ./openssl.tar.gz +mkdir -p /src/openssl /build/openssl +tar -xf openssl.tar.gz -C /src/openssl --strip 1 +cd /src/openssl +./config no-shared --static --prefix=/build/openssl --openssldir=/build/openssl +make +make install +ldconfig + +# Build nghttp2 from source (for Curl) +wget -q https://github.com/nghttp2/nghttp2/releases/download/v1.31.1/nghttp2-1.31.1.tar.gz -O ./nghttp2.tar.gz +mkdir -p /src/nghttp2 /build/nghttp2 +tar -xf nghttp2.tar.gz -C /src/nghttp2 --strip 1 +cd /src/nghttp2 +CFLAGS="-fPIC" ./configure --enable-lib-only --disable-shared --prefix=/build/nghttp2 +make +make install +ldconfig + +# Build Curl from source +wget -q https://github.com/curl/curl/releases/download/curl-7_61_1/curl-7.61.1.tar.gz -O ./curl.tar.gz +mkdir -p /src/curl +tar -xf curl.tar.gz -C /src/curl --strip 1 +cd /src/curl +./buildconf +LIBS="-ldl" CPPFLAGS="-I/build/openssl/include" LDFLAGS="-L/build/openssl/lib" \ + ./configure \ + --disable-shared \ + --enable-static \ + --with-ssl=/build/openssl \ + --with-nghttp2=/build/nghttp2 \ + --with-zlib=/build/zlib \ + --enable-ipv6 \ + --enable-unix-sockets +make +make install +ldconfig +curl --version diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..e25f5594af --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,85 @@ +name: CI + +on: [push] + +jobs: + Windows: + name: Windows + runs-on: windows-2016 + steps: + - name: Checkout branch + uses: actions/checkout@v1 + - name: Install NodeJS + uses: actions/setup-node@v1 + with: + node-version: 10 + - name: Install node tools + run: npm install --global --production windows-build-tools + - name: Install node-gyp + run: npm install --global node-gyp@latest + - name: Set node config to use python2.7 + run: npm config set python python2.7 + - name: Set node config to set msvs_version to 2015 + run: npm config set msvs_version 2015 + - name: Bootstrap packages + run: npm run bootstrap + - name: Run tests + run: npm test + - name: Release app + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REF: ${{ github.event.ref }} + CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }} + run: npm run app-release + # Disable for now in favor of TravisCI (this Mac version not old enough) + # Mac: + # name: Mac + # runs-on: macOS-10.14 + # steps: + # - name: Checkout branch + # uses: actions/checkout@v1 + # - name: Install NodeJS + # uses: actions/setup-node@v1 + # with: + # node-version: 10 + # - name: Bootstrap packages + # run: npm run bootstrap + # - name: Run tests + # run: npm test + # - name: Release app + # run: npm run app-release + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # GITHUB_REF: ${{ github.event.ref }} + # CSC_LINK: ${{ secrets.MAC_CSC_LINK }} + # CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }} + # APPLE_ID: ${{ secrets.APPLE_ID }} + # APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + Ubuntu14: + name: Linux (Generic) + runs-on: ubuntu-16.04 + steps: + - name: Checkout branch + uses: actions/checkout@v1 + - name: Run Docker + uses: ./.github/actions/build-linux + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REF: ${{ github.event.ref }} + BUILD_TARGETS: AppImage,deb,tar.gz,rpm + NODELIBCURL_BUILD_STATIC: yes + Ubuntu16: + name: Linux (Snap) + runs-on: ubuntu-16.04 + steps: + - name: Checkout branch + uses: actions/checkout@v1 + - name: Run Docker + uses: ./.github/actions/build-snap + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REF: ${{ github.event.ref }} + SNAPCRAFT_LOGIN_FILE: ${{ secrets.SNAPCRAFT_LOGIN_FILE }} + BUILD_TARGETS: snap + NODELIBCURL_BUILD_STATIC: yes diff --git a/.travis.yml b/.travis.yml index 0448d098d6..8efd07e05b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,70 +2,22 @@ language: node_js matrix: include: - - os: linux - sudo: required - dist: trusty - env: - - COMPOSE_SCRIPT=package_linux - - DOCKER_COMPOSE_VERSION=1.20.1 - before_install: - - sudo rm /usr/local/bin/docker-compose - - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - - chmod +x docker-compose - - sudo mv docker-compose /usr/local/bin - before_deploy: - - python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);' - - docker-compose build - - docker-compose build $COMPOSE_SCRIPT - - docker-compose run $COMPOSE_SCRIPT - - os: linux - sudo: required - dist: trusty - env: - - COMPOSE_SCRIPT=package_snap - - DOCKER_COMPOSE_VERSION=1.20.1 - before_install: - - sudo rm /usr/local/bin/docker-compose - - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - - chmod +x docker-compose - - sudo mv docker-compose /usr/local/bin - before_deploy: - - python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);' - - docker-compose build $COMPOSE_SCRIPT - - docker-compose run $COMPOSE_SCRIPT - - os: osx - env: - - CSC_LINK=$MAC_CSC_LINK - - CSC_KEY_PASSWORD=$MAC_CSC_KEY_PASSWORD - before_deploy: - - npm run app-package + - os: osx # High Sierra for older libcurl compatibility + osx_image: xcode10.1 + env: + - CSC_LINK=$MAC_CSC_LINK + - CSC_KEY_PASSWORD=$MAC_CSC_KEY_PASSWORD cache: directories: - - node_modules - - build/node_modules - - $HOME/.electron - - $HOME/.cache + - node_modules + - build/node_modules + - $HOME/.electron + - $HOME/.cache script: -- node --version -- npm --version -- npm run bootstrap -- npm test - -deploy: - provider: releases - api_key: $GITHUB_TOKEN - skip_cleanup: true - file_glob: true - prerelease: true - file: - - packages/insomnia-app/dist/**/*.zip - - packages/insomnia-app/dist/**/*.dmg - - packages/insomnia-app/dist/**/*.deb - - packages/insomnia-app/dist/**/*.snap - - packages/insomnia-app/dist/**/*.rpm - - packages/insomnia-app/dist/**/*.AppImage - - packages/insomnia-app/dist/**/*.tar.gz - on: - tags: true + - node --version + - npm --version + - npm run bootstrap + - npm test + - npm run app-release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 233cc2aa52..a8e7f47ddc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,8 @@ process easy and effective for everyone involved. ## Using the issue tracker -[GitHub Issues](https://github.com/getinsomnia/insomnia/issues) is the preferred channel -for [bug reports](#bug-reports), [features requests](#feature-requests) +[GitHub Issues](https://github.com/getinsomnia/insomnia/issues) is the preferred channel +for [bug reports](#bug-reports), [features requests](#feature-requests) and [submitting pull requests](#pull-requests). Please respect the following restrictions: @@ -34,14 +34,14 @@ Guidelines for bug reports: A good bug report should not leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is -your environment? What steps will reproduce the issue? What OS experienced the -problem? What would you expect to be the outcome? All these details will help +your environment? What steps will reproduce the issue? What OS experienced the +problem? What would you expect to be the outcome? All these details will help to fix any potential bugs. ## Feature Requests Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong +fits with the scope and aims of the project. It's up to _you_ to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. @@ -53,7 +53,7 @@ commits. **Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code, porting to a different language), -otherwise you risk spending a lot of time working on something that might +otherwise, you risk spending a lot of time working on something that might not get accepted into the project. **IMPORTANT**: By submitting a patch, you agree to allow the project owner to diff --git a/README.md b/README.md index ab11af603f..0cdef0c9f1 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,15 @@ npm test npm run app-start ``` +If you are on Linux and have problems, you may need to install `libfontconfig-dev` + +```bash +# Install libfontconfig-dev +sudo apt-get install libfontconfig-dev +``` + +If you are on Windows and have problems, you may need to install [Windows Build Tools](https://github.com/felixrieseberg/windows-build-tools) +
@@ -94,6 +103,7 @@ Here is a list of plugins available for installation via NPM. ## Community Projects +- [Insomnia Documenter](https://github.com/jozsefsallai/insomnia-documenter) – Generate beautiful API documentation pages using your Insomnia export file. - [GitHub API Spec Importer](https://github.com/swinton/github-rest-apis-for-insomnia) – A complete set of GitHub REST API route specifications that can be imported straight into Insomnia - [Swaggymnia](https://github.com/mlabouardy/swaggymnia) – Generate [Swagger](https://swagger.io/) documentation for your existing API in Insomnia. diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index eb57187c3f..0000000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: '3' -services: - package_linux: - build: - context: '.' - dockerfile: './docker/Dockerfile.Ubuntu14' - environment: - - 'NODELIBCURL_BUILD_STATIC=yes' - - 'BUILD_TARGETS=AppImage,deb,tar.gz,rpm' - - 'KEEP_DIST_FOLDER=yes' - volumes: - - ./packages/insomnia-app/dist:/src/insomnia/packages/insomnia-app/dist - package_snap: - build: - context: '.' - dockerfile: './docker/Dockerfile.Ubuntu16' - environment: - - 'NODELIBCURL_BUILD_STATIC=yes' - - 'BUILD_TARGETS=snap' - - 'KEEP_DIST_FOLDER=yes' - volumes: - - ./packages/insomnia-app/dist:/src/insomnia/packages/insomnia-app/dist diff --git a/docker/Dockerfile.Ubuntu14 b/docker/Dockerfile.Ubuntu14 deleted file mode 100644 index 388c32b5ab..0000000000 --- a/docker/Dockerfile.Ubuntu14 +++ /dev/null @@ -1,15 +0,0 @@ -FROM ubuntu:14.04 - -ADD docker/install-dependencies.sh /scripts/install-dependencies.sh -RUN /scripts/install-dependencies.sh - -# Setup dirs -ADD . /src/insomnia -WORKDIR /src/insomnia -VOLUME /src/insomnia/packages/insomnia-app/dist - -ADD docker/bootstrap.sh /scripts/bootstrap.sh -RUN /scripts/bootstrap.sh - -# Define build command -CMD npm run app-package diff --git a/docker/Dockerfile.Ubuntu16 b/docker/Dockerfile.Ubuntu16 deleted file mode 100644 index 4f68e4ec09..0000000000 --- a/docker/Dockerfile.Ubuntu16 +++ /dev/null @@ -1,15 +0,0 @@ -FROM ubuntu:16.04 - -ADD docker/install-dependencies.sh /scripts/install-dependencies.sh -RUN /scripts/install-dependencies.sh && apt-get install -y snapcraft - -# Setup dirs -ADD . /src/insomnia -WORKDIR /src/insomnia -VOLUME /src/insomnia/packages/insomnia-app/dist - -ADD docker/bootstrap.sh /scripts/bootstrap.sh -RUN /scripts/bootstrap.sh - -# Define build command -CMD npm run app-package diff --git a/docker/bootstrap.sh b/docker/bootstrap.sh deleted file mode 100755 index a4531e7b2d..0000000000 --- a/docker/bootstrap.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -# Fail on any errors -set -e - -# Install root project dependencies -npm run bootstrap -npm install --no-save 7zip-bin-linux app-builder-bin-linux - diff --git a/package-lock.json b/package-lock.json index b879512563..dba4a79870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,6 +74,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -111,7 +112,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -132,12 +134,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -152,17 +156,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -279,7 +286,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -291,6 +299,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -305,6 +314,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -312,12 +322,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -336,6 +348,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -416,7 +429,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -428,6 +442,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -513,7 +528,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -549,6 +565,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -568,6 +585,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -611,12 +629,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -647,7 +667,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "4.0.1", @@ -8988,7 +9009,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -9009,12 +9031,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9029,17 +9053,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -9156,7 +9183,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -9168,6 +9196,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9182,6 +9211,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -9189,12 +9219,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -9213,6 +9245,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -9293,7 +9326,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -9305,6 +9339,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -9390,7 +9425,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -9426,6 +9462,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9445,6 +9482,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9488,12 +9526,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -13922,9 +13962,9 @@ } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -15284,7 +15324,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "2.3.2", @@ -15547,7 +15588,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true }, "micromatch": { "version": "3.1.10", @@ -16240,9 +16282,9 @@ "dev": true }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -17239,38 +17281,15 @@ "dev": true }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "unique-filename": { diff --git a/package.json b/package.json index f10e2ff2db..2c44970043 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,12 @@ "app-start": "lerna run start --stream --parallel --bail", "app-build": "lerna run build --stream --parallel --bail", "app-package": "lerna run package --stream --parallel --bail", + "app-release": "lerna run release --stream --parallel --bail", "format-code": "prettier --write \"**/*.js\"" }, "husky": { "hooks": { - "pre-commit": "pretty-quick --staged" + "pre-commit": "pretty-quick --staged && npm run lint" } }, "devDependencies": { diff --git a/packages/insomnia-app/.electronbuilder b/packages/insomnia-app/.electronbuilder index 5f0b2081d5..e7c4f30a88 100644 --- a/packages/insomnia-app/.electronbuilder +++ b/packages/insomnia-app/.electronbuilder @@ -1,6 +1,7 @@ { - "appId": "com.insomnia.app", - "publish": false, + "appId": "__APP_ID__", + "publish": null, + "afterSign": "./scripts/afterSignHook.js", "extraResources": [ { "from": "./bin", @@ -23,7 +24,10 @@ "output": "dist" }, "mac": { + "hardenedRuntime": true, + "icon": "./build/icon.png", "category": "public.app-category.developer-tools", + "entitlements": "./build/static/entitlements.mac.inherit.plist", "target": [ "dmg", "zip" @@ -39,12 +43,12 @@ "zip" ] }, - "squirrelWindows":{ - "iconUrl": "https://github.com/getinsomnia/insomnia/blob/master/packages/insomnia-app/app/icons/icon.ico?raw=true" + "squirrelWindows": { + "iconUrl": "__ICON_URL__" }, "linux": { - "executableName": "insomnia", - "synopsis": "A simple, beautiful, and free REST API client", + "executableName": "__EXECUTABLE_NAME__", + "synopsis": "__SYNOPSIS__", "category": "Development", "target": [ "AppImage", diff --git a/packages/insomnia-app/app/common/__tests__/database.test.js b/packages/insomnia-app/app/common/__tests__/database.test.js index 0a163a906b..43a2716824 100644 --- a/packages/insomnia-app/app/common/__tests__/database.test.js +++ b/packages/insomnia-app/app/common/__tests__/database.test.js @@ -100,7 +100,7 @@ describe('requestCreate()', () => { }; const r = await models.request.create(patch); - expect(Object.keys(r).length).toBe(20); + expect(Object.keys(r).length).toBe(21); expect(r._id).toMatch(/^req_[a-zA-Z0-9]{32}$/); expect(r.created).toBeGreaterThanOrEqual(now); diff --git a/packages/insomnia-app/app/common/__tests__/render.test.js b/packages/insomnia-app/app/common/__tests__/render.test.js index f389267973..6914f1d322 100644 --- a/packages/insomnia-app/app/common/__tests__/render.test.js +++ b/packages/insomnia-app/app/common/__tests__/render.test.js @@ -311,6 +311,49 @@ describe('buildRenderContext()', () => { expect(context).toEqual({ parent: 'parent', test: 'parent grandparent' }); }); + it('merges nested properties when rendering', async () => { + const ancestors = [ + { + name: 'Parent', + type: models.requestGroup.type, + environment: { + parent: 'parent', + nested: { + common: 'parent', + parentA: 'pa', + parentB: 'pb', + }, + }, + }, + { + name: 'Grandparent', + type: models.requestGroup.type, + environment: { + test: '{{ parent }} grandparent', + nested: { + common: 'grandparent', + grandParentA: 'gpa', + grandParentB: 'gpb', + }, + }, + }, + ]; + + const context = await renderUtils.buildRenderContext(ancestors); + + expect(context).toEqual({ + parent: 'parent', + test: 'parent grandparent', + nested: { + common: 'parent', + grandParentA: 'gpa', + grandParentB: 'gpb', + parentA: 'pa', + parentB: 'pb', + }, + }); + }); + it('cascades properly and renders', async () => { const ancestors = [ { diff --git a/packages/insomnia-app/app/common/constants.js b/packages/insomnia-app/app/common/constants.js index 56d20670c8..eabfeac7d7 100644 --- a/packages/insomnia-app/app/common/constants.js +++ b/packages/insomnia-app/app/common/constants.js @@ -18,6 +18,10 @@ export function getAppName() { return packageJSON.app.productName; } +export function getAppId() { + return packageJSON.app.appId; +} + export function getAppPlatform() { return process.platform; } diff --git a/packages/insomnia-app/app/common/hotkeys.js b/packages/insomnia-app/app/common/hotkeys.js index 4dbb37c64e..efaf5142c3 100644 --- a/packages/insomnia-app/app/common/hotkeys.js +++ b/packages/insomnia-app/app/common/hotkeys.js @@ -487,3 +487,29 @@ export function constructKeyCombinationDisplay( } return joint; } + +/** + * Construct the display string for a key combination + * + * @param hotKeyDef + * @param hotKeyRegistry + * @param mustUsePlus + * @returns {string} – key combination as string or empty string if not found + */ +export function getHotKeyDisplay( + hotKeyDef: HotKeyDefinition, + hotKeyRegistry: HotKeyRegistry, + mustUsePlus: boolean, +) { + const hotKey: ?KeyBindings = hotKeyRegistry[hotKeyDef.id]; + if (!hotKey) { + return ''; + } + + const keyCombs: Array = getPlatformKeyCombinations(hotKey); + if (keyCombs.length === 0) { + return ''; + } + + return constructKeyCombinationDisplay(keyCombs[0], mustUsePlus); +} diff --git a/packages/insomnia-app/app/common/import.js b/packages/insomnia-app/app/common/import.js index de573bdd34..c9608fc331 100644 --- a/packages/insomnia-app/app/common/import.js +++ b/packages/insomnia-app/app/common/import.js @@ -6,14 +6,15 @@ import * as har from './har'; import type { BaseModel } from '../models/index'; import * as models from '../models/index'; import { getAppVersion } from './constants'; -import { showModal, showError } from '../ui/components/modals/index'; +import { showError, showModal } from '../ui/components/modals/index'; import AlertModal from '../ui/components/modals/alert-modal'; import fs from 'fs'; -import type { Workspace } from '../models/workspace'; -import type { Environment } from '../models/environment'; import { fnOrString, generateId } from './misc'; import YAML from 'yaml'; +const WORKSPACE_ID_KEY = '__WORKSPACE_ID__'; +const BASE_ENVIRONMENT_ID_KEY = '__BASE_ENVIRONMENT_ID__'; + const EXPORT_FORMAT = 4; const EXPORT_TYPE_REQUEST = 'request'; @@ -23,7 +24,7 @@ const EXPORT_TYPE_COOKIE_JAR = 'cookie_jar'; const EXPORT_TYPE_ENVIRONMENT = 'environment'; // If we come across an ID of this form, we will replace it with a new one -const REPLACE_ID_REGEX = /^__\w+_\d+__$/; +const REPLACE_ID_REGEX = /__\w+_\d+__/g; const MODELS = { [EXPORT_TYPE_REQUEST]: models.request, @@ -33,7 +34,14 @@ const MODELS = { [EXPORT_TYPE_ENVIRONMENT]: models.environment, }; -export async function importUri(workspaceId: string | null, uri: string): Promise { +export async function importUri( + getWorkspaceId: () => Promise, + uri: string, +): Promise<{ + source: string, + error: Error | null, + summary: { [string]: Array }, +}> { let rawText; if (uri.match(/^(http|https):\/\//)) { const response = await window.fetch(uri); @@ -45,7 +53,7 @@ export async function importUri(workspaceId: string | null, uri: string): Promis throw new Error(`Invalid import URI ${uri}`); } - const result = await importRaw(workspaceId, rawText); + const result = await importRaw(getWorkspaceId, rawText); const { summary, error } = result; if (error) { @@ -54,7 +62,7 @@ export async function importUri(workspaceId: string | null, uri: string): Promis error: error.message, message: 'Import failed', }); - return; + return result; } let statements = Object.keys(summary) @@ -72,12 +80,13 @@ export async function importUri(workspaceId: string | null, uri: string): Promis message = `You imported ${statements.join(', ')}!`; } showModal(AlertModal, { title: 'Import Succeeded', message }); + + return result; } export async function importRaw( - workspaceId: string | null, + getWorkspaceId: () => Promise, rawContent: string, - generateNewIds: boolean = false, ): Promise<{ source: string, error: Error | null, @@ -96,35 +105,40 @@ export async function importRaw( const { data } = results; - let workspace: Workspace | null = await models.workspace.getById(workspaceId || 'n/a'); - - // Fetch the base environment in case we need it - let baseEnvironment: Environment | null = await models.environment.getOrCreateForWorkspaceId( - workspaceId || 'n/a', - ); - // Generate all the ids we may need const generatedIds: { [string]: string | Function } = {}; for (const r of data.resources) { - if (generateNewIds || r._id.match(REPLACE_ID_REGEX)) { - generatedIds[r._id] = generateId(MODELS[r._type].prefix); + for (const key of r._id.match(REPLACE_ID_REGEX) || []) { + generatedIds[key] = generateId(MODELS[r._type].prefix); } } - // Always replace these "constants" - generatedIds['__WORKSPACE_ID__'] = async () => { - if (!workspace) { + // Contains the ID of the workspace to be used with the import + generatedIds[WORKSPACE_ID_KEY] = async () => { + const workspaceId = await getWorkspaceId(); + + // First try getting the workspace to overwrite + let workspace = await models.workspace.getById(workspaceId || 'n/a'); + + // If none provided, create a new workspace + if (workspace === null) { workspace = await models.workspace.create({ name: 'Imported Workspace' }); } + // Update this fn so it doesn't run again + generatedIds[WORKSPACE_ID_KEY] = workspace._id; + return workspace._id; }; - generatedIds['__BASE_ENVIRONMENT_ID__'] = async () => { - if (!baseEnvironment) { - const parentId = await fnOrString(generatedIds['__WORKSPACE_ID__']); - baseEnvironment = await models.environment.getOrCreateForWorkspaceId(parentId); - } + // Contains the ID of the base environment to be used with the import + generatedIds[BASE_ENVIRONMENT_ID_KEY] = async () => { + const parentId = await fnOrString(generatedIds[WORKSPACE_ID_KEY]); + const baseEnvironment = await models.environment.getOrCreateForWorkspaceId(parentId); + + // Update this fn so it doesn't run again + generatedIds[BASE_ENVIRONMENT_ID_KEY] = baseEnvironment._id; + return baseEnvironment._id; }; @@ -143,17 +157,20 @@ export async function importRaw( // Replace null parentIds with current workspace if (!resource.parentId && resource._type !== EXPORT_TYPE_WORKSPACE) { - resource.parentId = '__WORKSPACE_ID__'; + resource.parentId = WORKSPACE_ID_KEY; } - // Replace _id if we need to - if (generatedIds[resource._id]) { - resource._id = await fnOrString(generatedIds[resource._id]); - } + // Replace ID placeholders (eg. __WORKSPACE_ID__) with generated values + for (const key of Object.keys(generatedIds)) { + const { parentId, _id } = resource; - // Replace newly generated IDs if they exist - if (generatedIds[resource.parentId]) { - resource.parentId = await fnOrString(generatedIds[resource.parentId]); + if (parentId && parentId.includes(key)) { + resource.parentId = parentId.replace(key, await fnOrString(generatedIds[key])); + } + + if (_id && _id.includes(key)) { + resource._id = _id.replace(key, await fnOrString(generatedIds[key])); + } } const model: Object = MODELS[resource._type]; diff --git a/packages/insomnia-app/app/common/misc.js b/packages/insomnia-app/app/common/misc.js index 05a6061893..c912ddaadb 100644 --- a/packages/insomnia-app/app/common/misc.js +++ b/packages/insomnia-app/app/common/misc.js @@ -5,7 +5,7 @@ import fuzzysort from 'fuzzysort'; import uuid from 'uuid'; import zlib from 'zlib'; import { join as pathJoin } from 'path'; -import { DEBOUNCE_MILLIS } from './constants'; +import { METHOD_OPTIONS, METHOD_DELETE, DEBOUNCE_MILLIS } from './constants'; const ESCAPE_REGEX_MATCH = /[-[\]/{}()*+?.\\^$|]/g; @@ -117,6 +117,16 @@ export function removeVowels(str: string): string { return str.replace(/[aeiouyAEIOUY]/g, ''); } +export function formatMethodName(method: string): string { + let methodName = method; + if (method === METHOD_DELETE || method === METHOD_OPTIONS) { + methodName = method.slice(0, 3); + } else if (method.length > 4) { + methodName = removeVowels(method).slice(0, 4); + } + return methodName; +} + export function keyedDebounce(callback: Function, millis: number = DEBOUNCE_MILLIS): Function { let timeout; let results = {}; diff --git a/packages/insomnia-app/app/common/render.js b/packages/insomnia-app/app/common/render.js index 2c1d28b521..be8dfb7129 100644 --- a/packages/insomnia-app/app/common/render.js +++ b/packages/insomnia-app/app/common/render.js @@ -72,7 +72,7 @@ export async function buildRenderContext( * A regular Object.assign would yield { base_url: '{{ base_url }}/foo' } and the * original base_url of google.com would be lost. */ - if (Object.prototype.toString.call(subContext[key]) === '[object String]') { + if (Object.prototype.toString.call(subObject[key]) === '[object String]') { const isSelfRecursive = subObject[key].match(`{{ ?${key}[ |][^}]*}}`); if (isSelfRecursive) { @@ -247,19 +247,37 @@ export async function getRenderContext( ); const subEnvironment = await models.environment.getById(environmentId || 'n/a'); - let keySource = {}; - for (let key in (rootEnvironment || {}).data) { - keySource[key] = 'root'; - } + const keySource = {}; - if (subEnvironment) { - for (const key of Object.keys(subEnvironment.data || {})) { - if (subEnvironment.name) { - keySource[key] = subEnvironment.name; + // Function that gets Keys and stores their Source location + function getKeySource(subObject, inKey, inSource) { + // Add key to map if it's not root + if (inKey) { + keySource[inKey] = inSource; + } + + // Recurse down for Objects and Arrays + const typeStr = Object.prototype.toString.call(subObject); + if (typeStr === '[object Object]') { + for (const key of Object.keys(subObject)) { + getKeySource(subObject[key], inKey ? `${inKey}.${key}` : key, inSource); + } + } else if (typeStr === '[object Array]') { + for (let i = 0; i < subObject.length; i++) { + getKeySource(subObject[i], `${inKey}[${i}]`, inSource); } } } + // Get Keys from root environment + getKeySource((rootEnvironment || {}).data, '', 'root'); + + // Get Keys from sub environment + if (subEnvironment) { + getKeySource(subEnvironment.data || {}, '', subEnvironment.name || ''); + } + + // Get Keys from ancestors (e.g. Folders) if (ancestors) { for (let idx = 0; idx < ancestors.length; idx++) { let ancestor: any = ancestors[idx] || {}; @@ -268,9 +286,7 @@ export async function getRenderContext( ancestor.hasOwnProperty('environment') && ancestor.hasOwnProperty('name') ) { - for (const key of Object.keys(ancestor.environment || {})) { - keySource[key] = ancestor.name || ''; - } + getKeySource(ancestor.environment || {}, '', ancestor.name || ''); } } } @@ -396,6 +412,7 @@ export async function getRenderedRequestAndContext( settingSendCookies: renderedRequest.settingSendCookies, settingStoreCookies: renderedRequest.settingStoreCookies, settingRebuildPath: renderedRequest.settingRebuildPath, + settingFollowRedirects: renderedRequest.settingFollowRedirects, type: renderedRequest.type, url: renderedRequest.url, }, diff --git a/packages/insomnia-app/app/main/updates.js b/packages/insomnia-app/app/main/updates.js index 91900481b0..b663d222d6 100644 --- a/packages/insomnia-app/app/main/updates.js +++ b/packages/insomnia-app/app/main/updates.js @@ -3,6 +3,7 @@ import electron from 'electron'; import { CHECK_FOR_UPDATES_INTERVAL, getAppVersion, + getAppId, isDevelopment, UPDATE_URL_MAC, UPDATE_URL_WINDOWS, @@ -28,6 +29,7 @@ async function getUpdateUrl(force: boolean): Promise { const params = [ { name: 'v', value: getAppVersion() }, + { name: 'app', value: getAppId() }, { name: 'channel', value: settings.updateChannel }, ]; diff --git a/packages/insomnia-app/app/main/window-utils.js b/packages/insomnia-app/app/main/window-utils.js index aa289f1950..7a5e03eaab 100644 --- a/packages/insomnia-app/app/main/window-utils.js +++ b/packages/insomnia-app/app/main/window-utils.js @@ -34,21 +34,25 @@ export function createWindow() { const { bounds, fullscreen, maximize } = getBounds(); const { x, y, width, height } = bounds; - // Make sure we don't place the window outside of the visible space - let maxX = 0; - let maxY = 0; + let isVisibleOnAnyDisplay = true; for (const d of electron.screen.getAllDisplays()) { - // Set the maximum placement location to 50 pixels short of the end - maxX = Math.max(maxX, d.bounds.x + d.bounds.width - 50); - maxY = Math.max(maxY, d.bounds.y + d.bounds.height - 50); + const isVisibleOnDisplay = + x >= d.bounds.x && + y >= d.bounds.y && + x + width <= d.bounds.x + d.bounds.width && + y + height <= d.bounds.y + d.bounds.height; + + if (!isVisibleOnDisplay) { + isVisibleOnAnyDisplay = false; + } } - const finalX = Math.min(maxX, x); - const finalY = Math.min(maxX, y); mainWindow = new BrowserWindow({ // Make sure we don't initialize the window outside the bounds - x: finalX, - y: finalY, + x: isVisibleOnAnyDisplay ? x : undefined, + y: isVisibleOnAnyDisplay ? y : undefined, + + // Other options fullscreen: fullscreen, fullscreenable: true, title: getAppName(), diff --git a/packages/insomnia-app/app/models/__tests__/request.test.js b/packages/insomnia-app/app/models/__tests__/request.test.js index 628b7e859f..412ccebc28 100644 --- a/packages/insomnia-app/app/models/__tests__/request.test.js +++ b/packages/insomnia-app/app/models/__tests__/request.test.js @@ -21,6 +21,7 @@ describe('init()', () => { settingDisableRenderRequestBody: false, settingEncodeUrl: true, settingRebuildPath: true, + settingFollowRedirects: 'global', }); }); }); @@ -56,6 +57,7 @@ describe('create()', () => { settingDisableRenderRequestBody: false, settingEncodeUrl: true, settingRebuildPath: true, + settingFollowRedirects: 'global', }; expect(request).toEqual(expected); @@ -328,6 +330,7 @@ describe('migrate()', () => { settingDisableRenderRequestBody: false, settingEncodeUrl: true, settingRebuildPath: true, + settingFollowRedirects: 'global', }; const migrated = await models.initModel(models.request.type, original); diff --git a/packages/insomnia-app/app/models/environment.js b/packages/insomnia-app/app/models/environment.js index af86c6c3af..3387348edb 100644 --- a/packages/insomnia-app/app/models/environment.js +++ b/packages/insomnia-app/app/models/environment.js @@ -82,6 +82,20 @@ export function getById(id: string): Promise { return db.get(type, id); } +export async function duplicate(environment: Environment): Promise { + const name = `${environment.name} (Copy)`; + + // Get sort key of next environment + const q = { metaSortKey: { $gt: environment.metaSortKey } }; + const [nextEnvironment] = await db.find(type, q, { metaSortKey: 1 }); + const nextSortKey = nextEnvironment ? nextEnvironment.metaSortKey : environment.metaSortKey + 100; + + // Calculate new sort key + const metaSortKey = (environment.metaSortKey + nextSortKey) / 2; + + return db.duplicate(environment, { name, metaSortKey }); +} + export function remove(environment: Environment): Promise { return db.remove(environment); } diff --git a/packages/insomnia-app/app/models/request.js b/packages/insomnia-app/app/models/request.js index 259f9a72ff..63629cb7e2 100644 --- a/packages/insomnia-app/app/models/request.js +++ b/packages/insomnia-app/app/models/request.js @@ -85,6 +85,7 @@ type BaseRequest = { settingDisableRenderRequestBody: boolean, settingEncodeUrl: boolean, settingRebuildPath: boolean, + settingFollowRedirects: string, }; export type Request = BaseModel & BaseRequest; @@ -108,6 +109,7 @@ export function init(): BaseRequest { settingDisableRenderRequestBody: false, settingEncodeUrl: true, settingRebuildPath: true, + settingFollowRedirects: 'global', }; } diff --git a/packages/insomnia-app/app/models/settings.js b/packages/insomnia-app/app/models/settings.js index ee0e2474ef..428b11cf5b 100644 --- a/packages/insomnia-app/app/models/settings.js +++ b/packages/insomnia-app/app/models/settings.js @@ -1,4 +1,5 @@ // @flow +import * as packageJson from '../../package.json'; import type { BaseModel } from './index'; import * as db from '../common/database'; import { UPDATE_CHANNEL_STABLE } from '../common/constants'; @@ -71,7 +72,7 @@ export function init(): BaseSettings { validateSSL: true, forceVerticalLayout: false, autoHideMenuBar: false, - theme: 'default', + theme: packageJson.app.theme, pluginPath: '', nunjucksPowerUserMode: false, deviceId: null, diff --git a/packages/insomnia-app/app/network/__tests__/network.test.js b/packages/insomnia-app/app/network/__tests__/network.test.js index 656e014f6b..74fcba9308 100644 --- a/packages/insomnia-app/app/network/__tests__/network.test.js +++ b/packages/insomnia-app/app/network/__tests__/network.test.js @@ -102,7 +102,7 @@ describe('actuallySend()', () => { 'Accept: */*', 'Accept-Encoding:', ], - NOPROGRESS: false, + NOPROGRESS: true, USERNAME: 'user', PASSWORD: 'pass', POSTFIELDS: 'foo=bar', @@ -163,7 +163,7 @@ describe('actuallySend()', () => { 'Accept: */*', 'Accept-Encoding:', ], - NOPROGRESS: false, + NOPROGRESS: true, POSTFIELDS: 'foo=bar&bar=&=value', PROXY: '', TIMEOUT_MS: 0, @@ -252,7 +252,7 @@ describe('actuallySend()', () => { 'Accept: */*', 'Accept-Encoding:', ], - NOPROGRESS: false, + NOPROGRESS: true, USERNAME: 'user', PASSWORD: 'pass', POSTFIELDS: 'foo=bar', @@ -310,7 +310,7 @@ describe('actuallySend()', () => { 'Accept: */*', 'Accept-Encoding:', ], - NOPROGRESS: false, + NOPROGRESS: true, INFILESIZE_LARGE: 26, PROXY: '', READDATA: fs.readFileSync(fileName, 'utf8'), @@ -377,7 +377,7 @@ describe('actuallySend()', () => { 'Accept-Encoding:', ], INFILESIZE_LARGE: 244, - NOPROGRESS: false, + NOPROGRESS: true, READDATA: [ `--${DEFAULT_BOUNDARY}`, 'Content-Disposition: form-data; name="foo"; filename="testfile.txt"', @@ -434,7 +434,7 @@ describe('actuallySend()', () => { COOKIEFILE: '', FOLLOWLOCATION: true, HTTPHEADER: ['Accept: */*', 'Accept-Encoding:', 'content-type:'], - NOPROGRESS: false, + NOPROGRESS: true, PROXY: '', TIMEOUT_MS: 0, URL: 'http://my/path', @@ -478,7 +478,7 @@ describe('actuallySend()', () => { COOKIEFILE: '', FOLLOWLOCATION: true, HTTPHEADER: ['Accept: */*', 'Accept-Encoding:', 'content-type:'], - NOPROGRESS: false, + NOPROGRESS: true, PROXY: '', TIMEOUT_MS: 0, URL: 'http://localhost:3000/foo/bar', @@ -521,7 +521,7 @@ describe('actuallySend()', () => { COOKIEFILE: '', FOLLOWLOCATION: true, HTTPHEADER: ['Accept: */*', 'Accept-Encoding:', 'content-type:'], - NOPROGRESS: false, + NOPROGRESS: true, PROXY: '', TIMEOUT_MS: 0, URL: 'http://unix:3000/my/path', @@ -565,7 +565,7 @@ describe('actuallySend()', () => { COOKIEFILE: '', FOLLOWLOCATION: true, HTTPHEADER: ['Accept: */*', 'Accept-Encoding:', 'content-type:'], - NOPROGRESS: false, + NOPROGRESS: true, PROXY: '', TIMEOUT_MS: 0, NETRC: 2, diff --git a/packages/insomnia-app/app/network/network.js b/packages/insomnia-app/app/network/network.js index 5e75ad19a0..0789e9b8ad 100644 --- a/packages/insomnia-app/app/network/network.js +++ b/packages/insomnia-app/app/network/network.js @@ -213,13 +213,26 @@ export async function _actuallySend( }; // Set all the basic options - setOpt(Curl.option.FOLLOWLOCATION, settings.followRedirects); setOpt(Curl.option.VERBOSE, true); // True so debug function works - setOpt(Curl.option.NOPROGRESS, false); // False so progress function works + setOpt(Curl.option.NOPROGRESS, true); // True so curl doesn't print progress setOpt(Curl.option.ACCEPT_ENCODING, ''); // Auto decode everything enable(Curl.feature.NO_HEADER_PARSING); enable(Curl.feature.NO_DATA_PARSING); + // Set follow redirects setting + switch (renderedRequest.settingFollowRedirects) { + case 'off': + setOpt(Curl.option.FOLLOWLOCATION, false); + break; + case 'on': + setOpt(Curl.option.FOLLOWLOCATION, true); + break; + default: + // Set to global setting + setOpt(Curl.option.FOLLOWLOCATION, settings.followRedirects); + break; + } + // Set maximum amount of redirects allowed // NOTE: Setting this to -1 breaks some versions of libcurl if (settings.maxRedirects > 0) { @@ -286,26 +299,6 @@ export async function _actuallySend( // Set the headers (to be modified as we go) const headers = clone(renderedRequest.headers); - let lastPercent = 0; - // NOTE: This option was added in 7.32.0 so make it optional - setOpt( - Curl.option.XFERINFOFUNCTION, - (dltotal, dlnow, ultotal, ulnow) => { - if (dltotal === 0) { - return 0; - } - - const percent = Math.round((dlnow / dltotal) * 100); - if (percent !== lastPercent) { - // console.log(`[network] Request downloaded ${percent}%`); - lastPercent = percent; - } - - return 0; - }, - true, - ); - // Set the URL, including the query parameters const qs = buildQueryStringFromParams(renderedRequest.parameters); const url = joinUrlAndQueryString(renderedRequest.url, qs); @@ -711,7 +704,7 @@ export async function _actuallySend( // Update cookie jar if we need to and if we found any cookies if (renderedRequest.settingStoreCookies && setCookieStrings.length) { const cookies = await cookiesFromJar(jar); - models.cookieJar.update(renderedRequest.cookieJar, { cookies }); + await models.cookieJar.update(renderedRequest.cookieJar, { cookies }); } // Print informational message @@ -743,7 +736,8 @@ export async function _actuallySend( // Make sure the response body has been fully written first await waitForStreamToFinish(responseBodyWriteStream); - respond(responsePatch, responseBodyPath); + // Send response + await respond(responsePatch, responseBodyPath); }); curl.on('error', function(err, code) { diff --git a/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-authorization-code.test.js b/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-authorization-code.test.js index b94aa449ba..75697e394a 100644 --- a/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-authorization-code.test.js +++ b/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-authorization-code.test.js @@ -14,6 +14,8 @@ const CLIENT_SECRET = 'secret_12345456677756343'; const REDIRECT_URI = 'https://foo.com/redirect'; const SCOPE = 'scope_123'; const STATE = 'state_123'; +const AUDIENCE = 'https://foo.com/resource'; +const RESOURCE = 'foo.com'; describe('authorization_code', () => { beforeEach(globalBeforeEach); @@ -27,6 +29,8 @@ describe('authorization_code', () => { access_token: 'token_123', token_type: 'token_type', scope: SCOPE, + audience: AUDIENCE, + resource: RESOURCE, }), ); @@ -48,6 +52,8 @@ describe('authorization_code', () => { REDIRECT_URI, SCOPE, STATE, + AUDIENCE, + RESOURCE, ); // Check the request to fetch the token @@ -64,6 +70,8 @@ describe('authorization_code', () => { { name: 'code', value: 'code_123' }, { name: 'redirect_uri', value: REDIRECT_URI }, { name: 'state', value: STATE }, + { name: 'audience', value: AUDIENCE }, + { name: 'resource', value: RESOURCE }, ], }, headers: [ @@ -91,6 +99,8 @@ describe('authorization_code', () => { expires_in: null, token_type: 'token_type', scope: SCOPE, + audience: AUDIENCE, + resource: RESOURCE, error: null, error_uri: null, error_description: null, @@ -108,6 +118,8 @@ describe('authorization_code', () => { access_token: 'token_123', token_type: 'token_type', scope: SCOPE, + audience: AUDIENCE, + resource: RESOURCE, }), ); @@ -129,6 +141,8 @@ describe('authorization_code', () => { REDIRECT_URI, SCOPE, STATE, + AUDIENCE, + RESOURCE, ); // Check the request to fetch the token @@ -145,6 +159,8 @@ describe('authorization_code', () => { { name: 'code', value: 'code_123' }, { name: 'redirect_uri', value: REDIRECT_URI }, { name: 'state', value: STATE }, + { name: 'audience', value: AUDIENCE }, + { name: 'resource', value: RESOURCE }, { name: 'client_id', value: CLIENT_ID }, { name: 'client_secret', value: CLIENT_SECRET }, ], @@ -170,6 +186,8 @@ describe('authorization_code', () => { expires_in: null, token_type: 'token_type', scope: SCOPE, + audience: AUDIENCE, + resource: RESOURCE, error: null, error_uri: null, error_description: null, diff --git a/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-password.test.js b/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-password.test.js index 9e26ec0cfc..ae2be0e8ae 100644 --- a/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-password.test.js +++ b/packages/insomnia-app/app/network/o-auth-2/__tests__/grant-password.test.js @@ -12,6 +12,7 @@ const CLIENT_SECRET = 'secret_12345456677756343'; const USERNAME = 'user'; const PASSWORD = 'password'; const SCOPE = 'scope_123'; +const AUDIENCE = 'https://foo.com/userinfo'; describe('password', () => { beforeEach(globalBeforeEach); @@ -24,6 +25,7 @@ describe('password', () => { access_token: 'token_123', token_type: 'token_type', scope: SCOPE, + audience: AUDIENCE, }), ); @@ -44,6 +46,7 @@ describe('password', () => { USERNAME, PASSWORD, SCOPE, + AUDIENCE, ); // Check the request to fetch the token @@ -60,6 +63,7 @@ describe('password', () => { { name: 'username', value: USERNAME }, { name: 'password', value: PASSWORD }, { name: 'scope', value: SCOPE }, + { name: 'audience', value: AUDIENCE }, ], }, headers: [ @@ -87,6 +91,7 @@ describe('password', () => { token_type: 'token_type', refresh_token: null, scope: SCOPE, + audience: AUDIENCE, error: null, error_uri: null, error_description: null, @@ -103,6 +108,7 @@ describe('password', () => { access_token: 'token_123', token_type: 'token_type', scope: SCOPE, + audience: AUDIENCE, }), ); @@ -123,6 +129,7 @@ describe('password', () => { USERNAME, PASSWORD, SCOPE, + AUDIENCE, ); // Check the request to fetch the token @@ -139,6 +146,7 @@ describe('password', () => { { name: 'username', value: USERNAME }, { name: 'password', value: PASSWORD }, { name: 'scope', value: SCOPE }, + { name: 'audience', value: AUDIENCE }, { name: 'client_id', value: CLIENT_ID }, { name: 'client_secret', value: CLIENT_SECRET }, ], @@ -164,6 +172,7 @@ describe('password', () => { token_type: 'token_type', refresh_token: null, scope: SCOPE, + audience: AUDIENCE, error: null, error_uri: null, error_description: null, diff --git a/packages/insomnia-app/app/network/o-auth-2/get-token.js b/packages/insomnia-app/app/network/o-auth-2/get-token.js index a6824780d2..e8756c55fc 100644 --- a/packages/insomnia-app/app/network/o-auth-2/get-token.js +++ b/packages/insomnia-app/app/network/o-auth-2/get-token.js @@ -63,6 +63,8 @@ async function _getOAuth2AuthorizationCodeHeader( authentication.redirectUrl, authentication.scope, authentication.state, + authentication.audience, + authentication.resource, ); return _updateOAuth2Token(requestId, results); @@ -138,6 +140,7 @@ async function _getOAuth2PasswordHeader( authentication.username, authentication.password, authentication.scope, + authentication.audience, ); return _updateOAuth2Token(requestId, results); diff --git a/packages/insomnia-app/app/network/o-auth-2/grant-authorization-code.js b/packages/insomnia-app/app/network/o-auth-2/grant-authorization-code.js index 0e6dee7d35..76de98c023 100644 --- a/packages/insomnia-app/app/network/o-auth-2/grant-authorization-code.js +++ b/packages/insomnia-app/app/network/o-auth-2/grant-authorization-code.js @@ -18,6 +18,8 @@ export default async function( redirectUri: string = '', scope: string = '', state: string = '', + audience: string = '', + resource: string = '', ): Promise { if (!authorizeUrl) { throw new Error('Invalid authorization URL'); @@ -27,7 +29,15 @@ export default async function( throw new Error('Invalid access token URL'); } - const authorizeResults = await _authorize(authorizeUrl, clientId, redirectUri, scope, state); + const authorizeResults = await _authorize( + authorizeUrl, + clientId, + redirectUri, + scope, + state, + audience, + resource, + ); // Handle the error if (authorizeResults[c.P_ERROR]) { @@ -46,10 +56,20 @@ export default async function( authorizeResults[c.P_CODE], redirectUri, state, + audience, + resource, ); } -async function _authorize(url, clientId, redirectUri = '', scope = '', state = '') { +async function _authorize( + url, + clientId, + redirectUri = '', + scope = '', + state = '', + audience = '', + resource = '', +) { const params = [ { name: c.P_RESPONSE_TYPE, value: c.RESPONSE_TYPE_CODE }, { name: c.P_CLIENT_ID, value: clientId }, @@ -59,6 +79,8 @@ async function _authorize(url, clientId, redirectUri = '', scope = '', state = ' redirectUri && params.push({ name: c.P_REDIRECT_URI, value: redirectUri }); scope && params.push({ name: c.P_SCOPE, value: scope }); state && params.push({ name: c.P_STATE, value: state }); + audience && params.push({ name: c.P_AUDIENCE, value: audience }); + resource && params.push({ name: c.P_RESOURCE, value: resource }); // Add query params to URL const qs = buildQueryStringFromParams(params); @@ -89,6 +111,8 @@ async function _getToken( code: string, redirectUri: string = '', state: string = '', + audience: string = '', + resource: string = '', ): Promise { const params = [ { name: c.P_GRANT_TYPE, value: c.GRANT_TYPE_AUTHORIZATION_CODE }, @@ -98,6 +122,8 @@ async function _getToken( // Add optional params redirectUri && params.push({ name: c.P_REDIRECT_URI, value: redirectUri }); state && params.push({ name: c.P_STATE, value: state }); + audience && params.push({ name: c.P_AUDIENCE, value: audience }); + resource && params.push({ name: c.P_RESOURCE, value: resource }); const headers = [ { name: 'Content-Type', value: 'application/x-www-form-urlencoded' }, @@ -145,6 +171,8 @@ async function _getToken( c.P_EXPIRES_IN, c.P_TOKEN_TYPE, c.P_SCOPE, + c.P_AUDIENCE, + c.P_RESOURCE, c.P_ERROR, c.P_ERROR_URI, c.P_ERROR_DESCRIPTION, diff --git a/packages/insomnia-app/app/network/o-auth-2/grant-password.js b/packages/insomnia-app/app/network/o-auth-2/grant-password.js index 7dccc0e2e4..1f49047bd3 100644 --- a/packages/insomnia-app/app/network/o-auth-2/grant-password.js +++ b/packages/insomnia-app/app/network/o-auth-2/grant-password.js @@ -15,6 +15,7 @@ export default async function( username: string, password: string, scope: string = '', + audience: string = '', ): Promise { const params = [ { name: c.P_GRANT_TYPE, value: c.GRANT_TYPE_PASSWORD }, @@ -24,6 +25,7 @@ export default async function( // Add optional params scope && params.push({ name: c.P_SCOPE, value: scope }); + audience && params.push({ name: c.P_AUDIENCE, value: audience }); const headers = [ { name: 'Content-Type', value: 'application/x-www-form-urlencoded' }, @@ -73,6 +75,7 @@ export default async function( c.P_EXPIRES_IN, c.P_REFRESH_TOKEN, c.P_SCOPE, + c.P_AUDIENCE, c.P_ERROR, c.P_ERROR_URI, c.P_ERROR_DESCRIPTION, diff --git a/packages/insomnia-app/app/network/o-auth-2/refresh-token.js b/packages/insomnia-app/app/network/o-auth-2/refresh-token.js index 3d4136db2d..877d5b0209 100644 --- a/packages/insomnia-app/app/network/o-auth-2/refresh-token.js +++ b/packages/insomnia-app/app/network/o-auth-2/refresh-token.js @@ -48,6 +48,7 @@ export default async function( }); const statusCode = response.statusCode || 0; + const bodyBuffer = models.response.getBodyBuffer(response); if (statusCode === 401) { // If the refresh token was rejected due an unauthorized request, we will @@ -56,10 +57,21 @@ export default async function( return responseToObject(null, [c.P_ACCESS_TOKEN]); } else if (statusCode < 200 || statusCode >= 300) { + if (bodyBuffer && statusCode === 400) { + const response = responseToObject(bodyBuffer.toString(), [c.P_ERROR, c.P_ERROR_DESCRIPTION]); + + // If the refresh token was rejected due an oauth2 invalid_grant error, we will + // return a null access_token to trigger an authentication request to fetch + // brand new refresh and access tokens. + + if (response[c.P_ERROR] === 'invalid_grant') { + return responseToObject(null, [c.P_ACCESS_TOKEN]); + } + } + throw new Error(`[oauth2] Failed to refresh token url=${url} status=${statusCode}`); } - const bodyBuffer = models.response.getBodyBuffer(response); if (!bodyBuffer) { throw new Error(`[oauth2] No body returned from ${url}`); } diff --git a/packages/insomnia-app/app/plugins/context/__fixtures__/basic-import.json b/packages/insomnia-app/app/plugins/context/__fixtures__/basic-import.json index 834dae6a62..a7fbee1aa6 100644 --- a/packages/insomnia-app/app/plugins/context/__fixtures__/basic-import.json +++ b/packages/insomnia-app/app/plugins/context/__fixtures__/basic-import.json @@ -30,6 +30,7 @@ "settingEncodeUrl": true, "settingSendCookies": true, "settingStoreCookies": true, + "settingFollowRedirects": "global", "url": "https://insomnia.rest", "_type": "request" } diff --git a/packages/insomnia-app/app/plugins/context/__tests__/data.test.js b/packages/insomnia-app/app/plugins/context/__tests__/data.test.js index 8830511682..48038033c5 100644 --- a/packages/insomnia-app/app/plugins/context/__tests__/data.test.js +++ b/packages/insomnia-app/app/plugins/context/__tests__/data.test.js @@ -76,6 +76,7 @@ describe('app.import.*', () => { settingSendCookies: true, settingStoreCookies: true, settingRebuildPath: true, + settingFollowRedirects: 'global', type: 'Request', url: 'https://insomnia.rest', }, @@ -125,6 +126,7 @@ describe('app.import.*', () => { settingSendCookies: true, settingStoreCookies: true, settingRebuildPath: true, + settingFollowRedirects: 'global', type: 'Request', url: 'https://insomnia.rest', }, @@ -199,6 +201,7 @@ describe('app.export.*', () => { settingSendCookies: true, settingStoreCookies: true, settingRebuildPath: true, + settingFollowRedirects: 'global', url: 'https://insomnia.rest', }, ]), diff --git a/packages/insomnia-app/app/plugins/context/__tests__/request.test.js b/packages/insomnia-app/app/plugins/context/__tests__/request.test.js index 6596439505..59e3816ee6 100644 --- a/packages/insomnia-app/app/plugins/context/__tests__/request.test.js +++ b/packages/insomnia-app/app/plugins/context/__tests__/request.test.js @@ -52,6 +52,7 @@ describe('init()', () => { 'setUrl', 'settingDisableRenderRequestBody', 'settingEncodeUrl', + 'settingFollowRedirects', 'settingSendCookies', 'settingStoreCookies', ]); diff --git a/packages/insomnia-app/app/plugins/context/data.js b/packages/insomnia-app/app/plugins/context/data.js index 8f62fd9fb2..c25333a447 100644 --- a/packages/insomnia-app/app/plugins/context/data.js +++ b/packages/insomnia-app/app/plugins/context/data.js @@ -10,10 +10,10 @@ export function init(): { import: Object, export: Object } { return { import: { async uri(uri: string, options: { workspaceId?: string } = {}): Promise { - await importUri(options.workspaceId || null, uri); + await importUri(() => Promise.resolve(options.workspaceId || null), uri); }, async raw(text: string, options: { workspaceId?: string } = {}): Promise { - await importRaw(options.workspaceId || null, text); + await importRaw(() => Promise.resolve(options.workspaceId || null), text); }, }, export: { diff --git a/packages/insomnia-app/app/plugins/context/request.js b/packages/insomnia-app/app/plugins/context/request.js index ac9546a406..7f2ef86428 100644 --- a/packages/insomnia-app/app/plugins/context/request.js +++ b/packages/insomnia-app/app/plugins/context/request.js @@ -62,6 +62,9 @@ export function init( settingDisableRenderRequestBody(enabled: boolean) { renderedRequest.settingDisableRenderRequestBody = enabled; }, + settingFollowRedirects(enabled: string) { + renderedRequest.settingFollowRedirects = enabled; + }, getHeader(name: string): string | null { const headers = misc.filterHeaders(renderedRequest.headers, name); if (headers.length) { @@ -160,6 +163,7 @@ export function init( delete request.settingStoreCookies; delete request.settingEncodeUrl; delete request.settingDisableRenderRequestBody; + delete request.settingFollowRedirects; delete request.removeHeader; delete request.setHeader; delete request.addHeader; diff --git a/packages/insomnia-app/app/plugins/index.js b/packages/insomnia-app/app/plugins/index.js index 3f4ff43d01..e45430ffbf 100644 --- a/packages/insomnia-app/app/plugins/index.js +++ b/packages/insomnia-app/app/plugins/index.js @@ -1,5 +1,6 @@ // @flow import mkdirp from 'mkdirp'; +import * as packageJson from '../../package.json'; import * as models from '../models'; import fs from 'fs'; import path from 'path'; @@ -52,20 +53,6 @@ export type Theme = { theme: PluginTheme, }; -const CORE_PLUGINS = [ - 'insomnia-plugin-base64', - 'insomnia-plugin-hash', - 'insomnia-plugin-file', - 'insomnia-plugin-now', - 'insomnia-plugin-uuid', - 'insomnia-plugin-prompt', - 'insomnia-plugin-request', - 'insomnia-plugin-response', - 'insomnia-plugin-jsonpath', - 'insomnia-plugin-cookie-jar', - 'insomnia-plugin-core-themes', -]; - let plugins: ?Array = null; export async function init(): Promise { @@ -157,7 +144,7 @@ export async function getPlugins(force: boolean = false): Promise> // "name": "module" }; - for (const p of CORE_PLUGINS) { + for (const p of packageJson.app.plugins) { const pluginJson = global.require(`${p}/package.json`); const pluginModule = global.require(p); pluginMap[pluginJson.name] = _initPlugin(pluginJson, pluginModule); diff --git a/packages/insomnia-app/app/plugins/install.js b/packages/insomnia-app/app/plugins/install.js index fbb690b367..22945b1d76 100644 --- a/packages/insomnia-app/app/plugins/install.js +++ b/packages/insomnia-app/app/plugins/install.js @@ -63,7 +63,7 @@ async function _isInsomniaPlugin(lookupName: string): Promise { escape(process.execPath), [ '--no-deprecation', // Because Yarn still uses `new Buffer()` - _getYarnPath(), + escape(_getYarnPath()), 'info', lookupName, '--json', @@ -129,13 +129,13 @@ async function _installPluginToTmpDir(lookupName: string): Promise<{ tmpDir: str escape(process.execPath), [ '--no-deprecation', // Because Yarn still uses `new Buffer()` - _getYarnPath(), + escape(_getYarnPath()), 'add', lookupName, '--modules-folder', - tmpDir, + escape(tmpDir), '--cwd', - tmpDir, + escape(tmpDir), '--no-lockfile', '--production', '--no-progress', diff --git a/packages/insomnia-app/app/renderer.html b/packages/insomnia-app/app/renderer.html index d0b7b1c98a..5dcb40ec69 100644 --- a/packages/insomnia-app/app/renderer.html +++ b/packages/insomnia-app/app/renderer.html @@ -2,7 +2,6 @@ - Insomnia + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + \ No newline at end of file diff --git a/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js b/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js index 55aa5616ab..c52c6ab1f9 100644 --- a/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js +++ b/packages/insomnia-app/app/ui/components/base/dropdown/dropdown.js @@ -218,13 +218,18 @@ class Dropdown extends PureComponent { _getFlattenedChildren(children) { let newChildren = []; + // Ensure children is an array + children = Array.isArray(children) ? children : [children]; + for (const child of children) { if (!child) { // Ignore null components continue; } - if (Array.isArray(child)) { + if (child.type === React.Fragment) { + newChildren = [...newChildren, ...this._getFlattenedChildren(child.props.children)]; + } else if (Array.isArray(child)) { newChildren = [...newChildren, ...this._getFlattenedChildren(child)]; } else { newChildren.push(child); @@ -305,8 +310,7 @@ class Dropdown extends PureComponent { const dropdownButtons = []; const dropdownItems = []; - const listedChildren = Array.isArray(children) ? children : [children]; - const allChildren = this._getFlattenedChildren(listedChildren); + const allChildren = this._getFlattenedChildren(children); const visibleChildren = allChildren.filter((child, i) => { if (child.type.name !== DropdownItem.name) { diff --git a/packages/insomnia-app/app/ui/components/codemirror/code-editor.js b/packages/insomnia-app/app/ui/components/codemirror/code-editor.js index fa6ad498ef..c14b8c1ccb 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/code-editor.js +++ b/packages/insomnia-app/app/ui/components/codemirror/code-editor.js @@ -152,9 +152,9 @@ class CodeEditor extends React.Component { } } - setSelection(chStart, chEnd, line = 0) { + setSelection(chStart, chEnd, lineStart, lineEnd) { if (this.codeMirror) { - this.codeMirror.setSelection({ line, ch: chStart }, { line, ch: chEnd }); + this.codeMirror.setSelection({ line: lineStart, ch: chStart }, { line: lineEnd, ch: chEnd }); } } @@ -294,9 +294,15 @@ class CodeEditor extends React.Component { this.codeMirror.setCursor({ line: -1, ch: -1 }); this.codeMirror.setOption('extraKeys', { + ...BASE_CODEMIRROR_OPTIONS.extraKeys, Tab: cm => { - const spaces = this._indentChars(); - cm.replaceSelection(spaces); + // Indent with tabs or spaces + // From https://github.com/codemirror/CodeMirror/issues/988#issuecomment-14921785 + if (cm.somethingSelected()) { + cm.indentSelection('add'); + } else { + cm.replaceSelection(this._indentChars(), 'end', '+input'); + } }, }); diff --git a/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.js b/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.js index 9dc111bb1e..a725076fa6 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.js +++ b/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.js @@ -240,16 +240,16 @@ async function _updateElementText(render, mark, text, renderContext, isVariableU let dataIgnore = ''; let dataError = ''; - try { - const str = text.replace(/\\/g, ''); - const tagMatch = str.match(/{% *([^ ]+) *.*%}/); - const cleanedStr = str - .replace(/^{%/, '') - .replace(/%}$/, '') - .replace(/^{{/, '') - .replace(/}}$/, '') - .trim(); + const str = text.replace(/\\/g, ''); + const tagMatch = str.match(/{% *([^ ]+) *.*%}/); + const cleanedStr = str + .replace(/^{%/, '') + .replace(/%}$/, '') + .replace(/^{{/, '') + .replace(/}}$/, '') + .trim(); + try { if (tagMatch) { const tagData = tokenizeTag(str); const tagDefinition = (await getTagDefinitions()).find(d => d.name === tagData.name); @@ -281,9 +281,7 @@ async function _updateElementText(render, mark, text, renderContext, isVariableU } dataError = 'off'; } catch (err) { - const fullMessage = err.message.replace(/\[.+,.+]\s*/, ''); - let message = fullMessage; - title = message; + title = err.message.replace(/\[.+,.+]\s*/, ''); dataError = 'on'; } @@ -291,7 +289,7 @@ async function _updateElementText(render, mark, text, renderContext, isVariableU el.setAttribute('data-ignore', dataIgnore); if (dataError === 'on') { el.setAttribute('data-error', dataError); - el.innerHTML = '' + innerHTML; + el.innerHTML = '' + cleanedStr; } else { el.innerHTML = '' + innerHTML; } diff --git a/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js b/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js index 72e4127c76..608f040482 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js +++ b/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.js @@ -241,7 +241,7 @@ class OneLineEditor extends PureComponent { const check = () => { if (this._editor) { this._editor.focus(); - this._editor.setSelection(start, end); + this._editor.setSelection(start, end, 0, 0); } else { setTimeout(check, 40); } diff --git a/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js index cb5af70709..49692ba421 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/response-history-dropdown.js @@ -1,6 +1,7 @@ // @flow import * as React from 'react'; import autobind from 'autobind-decorator'; +import moment from 'moment'; import { Dropdown, DropdownButton, DropdownDivider, DropdownItem } from '../base/dropdown'; import StatusTag from '../tags/status-tag'; import URLTag from '../tags/url-tag'; @@ -82,6 +83,41 @@ class ResponseHistoryDropdown extends React.PureComponent { ); } + renderPastResponses(responses: Array) { + const now = moment(); + // Four arrays for four time groups + const categories = [[], [], [], []]; + responses.forEach(r => { + const resTime = moment(r.modified); + if (now.diff(resTime, 'minutes') < 5) { + // Five minutes ago + categories[0].push(r); + } else if (now.diff(resTime, 'hours') < 2) { + // Two hours ago + categories[1].push(r); + } else if (now.isSame(resTime, 'day')) { + // Today + categories[2].push(r); + } else if (now.isSame(resTime, 'week')) { + // This week + categories[3].push(r); + } + }); + + return ( + + 5 Minutes Ago + {categories[0].map(this.renderDropdownItem)} + 2 Hours Ago + {categories[1].map(this.renderDropdownItem)} + Today + {categories[2].map(this.renderDropdownItem)} + This Week + {categories[3].map(this.renderDropdownItem)} + + ); + } + render() { const { activeResponse, // eslint-disable-line no-unused-vars @@ -117,8 +153,7 @@ class ResponseHistoryDropdown extends React.PureComponent { Clear History - Past Responses - {responses.map(this.renderDropdownItem)} + {this.renderPastResponses(responses)} ); diff --git a/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js index cedadfcb5d..7a0eadd246 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.js @@ -9,7 +9,7 @@ import DropdownItem from '../base/dropdown/dropdown-item'; import DropdownHint from '../base/dropdown/dropdown-hint'; import SettingsModal, { TAB_INDEX_EXPORT } from '../modals/settings-modal'; import * as models from '../../../models'; -import { getAppVersion } from '../../../common/constants'; +import { getAppName, getAppVersion } from '../../../common/constants'; import { showAlert, showModal, showPrompt } from '../modals'; import Link from '../base/link'; import WorkspaceSettingsModal from '../modals/workspace-settings-modal'; @@ -68,7 +68,7 @@ class WorkspaceDropdown extends React.PureComponent { for (const workspace of this.props.unseenWorkspaces) { const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspace._id); if (!workspaceMeta.hasSeen) { - models.workspaceMeta.update(workspaceMeta, { hasSeen: true }); + await models.workspaceMeta.update(workspaceMeta, { hasSeen: true }); } } } @@ -307,7 +307,9 @@ class WorkspaceDropdown extends React.PureComponent { ))} - Insomnia Version {getAppVersion()} + + {getAppName()} v{getAppVersion()} + Preferences diff --git a/packages/insomnia-app/app/ui/components/editors/auth/o-auth-2-auth.js b/packages/insomnia-app/app/ui/components/editors/auth/o-auth-2-auth.js index d94b9d8267..1332d6fa4c 100644 --- a/packages/insomnia-app/app/ui/components/editors/auth/o-auth-2-auth.js +++ b/packages/insomnia-app/app/ui/components/editors/auth/o-auth-2-auth.js @@ -425,7 +425,7 @@ class OAuth2Auth extends React.PureComponent { enabled, ]; - advancedFields = [scope, state, credentialsInBody, tokenPrefix]; + advancedFields = [scope, state, credentialsInBody, tokenPrefix, audience, resource]; } else if (grantType === GRANT_TYPE_CLIENT_CREDENTIALS) { basicFields = [accessTokenUrl, clientId, clientSecret, enabled]; @@ -433,7 +433,7 @@ class OAuth2Auth extends React.PureComponent { } else if (grantType === GRANT_TYPE_PASSWORD) { basicFields = [username, password, accessTokenUrl, clientId, clientSecret, enabled]; - advancedFields = [scope, credentialsInBody, tokenPrefix]; + advancedFields = [scope, credentialsInBody, tokenPrefix, audience]; } else if (grantType === GRANT_TYPE_IMPLICIT) { basicFields = [authorizationUrl, clientId, redirectUri, enabled]; diff --git a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js index 8011ab5962..f61287f2f2 100644 --- a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js +++ b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.js @@ -227,7 +227,7 @@ class GraphQLEditor extends React.PureComponent { const { response } = schemaFetchError; showModal(ResponseDebugModal, { - title: 'Introspection Request', + title: 'GraphQL Introspection Response', response: response, }); } @@ -345,8 +345,12 @@ class GraphQLEditor extends React.PureComponent { }); } - async _handleRefreshSchema(): Promise { - await this._fetchAndSetSchema(this.props.request); + _handleRefreshSchema() { + // First, "forget" preference to hide errors so they always show + // again after a refresh + this.setState({ hideSchemaFetchErrors: false }, async () => { + await this._fetchAndSetSchema(this.props.request); + }); } async _handleToggleAutomaticFetching(): Promise { @@ -395,6 +399,20 @@ class GraphQLEditor extends React.PureComponent { return; } + try { + this._documentAST = parse(query); + } catch (e) { + this._documentAST = null; + } + + // Find op if there isn't one yet + if (!body.operationName) { + const newOperationName = this._getCurrentOperation(); + if (newOperationName) { + body.operationName = newOperationName; + } + } + this.setState({ variablesSyntaxError: '', body, @@ -402,17 +420,13 @@ class GraphQLEditor extends React.PureComponent { this.props.onChange(newContent); this._highlightOperation(body.operationName || null); - - try { - this._documentAST = parse(query); - } catch (e) { - this._documentAST = null; - } } _handleQueryChange(query: string): void { - const currentOperation = this._getCurrentOperation(); - this._handleBodyChange(query, this.state.body.variables, currentOperation); + // Since we're editing the query, we may be changing the operation name, so + // Don't pass it to the body change in order to automatically re-detect it + // based on the current cursor position. + this._handleBodyChange(query, this.state.body.variables, null); } _handleVariablesChange(variables: string): void { diff --git a/packages/insomnia-app/app/ui/components/editors/request-headers-editor.js b/packages/insomnia-app/app/ui/components/editors/request-headers-editor.js index 2033b955e2..063aaeead1 100644 --- a/packages/insomnia-app/app/ui/components/editors/request-headers-editor.js +++ b/packages/insomnia-app/app/ui/components/editors/request-headers-editor.js @@ -129,8 +129,8 @@ class RequestHeadersEditor extends React.PureComponent {
{ + renderDescription() { + const { type } = this.props; + return ; + } + + renderValues() { + const { type } = this.props; + + const values = type.getValues(); + + return ( + +

Values

+
    + {values.map((value: GraphQLEnumValue) => { + const description = + value.description || + 'This is a long paragraph that is a description for the enum value ' + value.name; + return ( +
  • + {value.name} + {description && ( +
    + +
    + )} +
  • + ); + })} +
+
+ ); + } + + render() { + return ( +
+ {this.renderDescription()} + {this.renderValues()} +
+ ); + } +} + +export default GraphQLExplorerEnum; diff --git a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js index a8d0e0dbb6..af9773368f 100644 --- a/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js +++ b/packages/insomnia-app/app/ui/components/graph-ql-explorer/graph-ql-explorer.js @@ -4,21 +4,23 @@ import autobind from 'autobind-decorator'; import GraphQLExplorerField from './graph-ql-explorer-field'; import GraphQLExplorerType from './graph-ql-explorer-type'; import type { GraphQLArgument, GraphQLField, GraphQLSchema, GraphQLType } from 'graphql'; +import { GraphQLEnumType } from 'graphql'; import GraphQLExplorerSchema from './graph-ql-explorer-schema'; +import GraphQLExplorerEnum from './graph-ql-explorer-enum'; type Props = { handleClose: () => void, schema: GraphQLSchema | null, visible: boolean, reference: null | { - type: GraphQLType | null, + type: GraphQLType | GraphQLEnumType | null, argument: GraphQLArgument | null, field: GraphQLField | null, }, }; type HistoryItem = { - currentType: null | GraphQLType, + currentType: null | GraphQLType | GraphQLEnumType, currentField: null | GraphQLField, }; @@ -37,7 +39,7 @@ class GraphQLExplorer extends React.PureComponent { }; } - _handleNavigateType(type: GraphQLType) { + _handleNavigateType(type: GraphQLType | GraphQLEnumType) { this.setState({ currentType: type, currentField: null, @@ -160,6 +162,8 @@ class GraphQLExplorer extends React.PureComponent { child = ( ); + } else if (currentType && currentType instanceof GraphQLEnumType) { + child = ; } else if (currentType) { child = ( { render() { - const { email, size: rawSize, className } = this.props; + const { email, size: rawSize, className, fallback } = this.props; const size = rawSize || 100; - const sanitizedEmail = email.trim().toLowerCase(); - const hash = crypto - .createHash('md5') - .update(sanitizedEmail) - .digest('hex'); - const url = `https://www.gravatar.com/avatar/${hash}?s=${size * 2}`; + let src = fallback; + + if (email) { + const hash = crypto + .createHash('md5') + .update(email.trim().toLowerCase()) + .digest('hex'); + src = `https://www.gravatar.com/avatar/${hash}?s=${size * 2}`; + } + const cssSize = `${size}px`; return ( Profile picture ); } } -GravatarImg.propTypes = { - // Required - email: PropTypes.string.isRequired, - - // Optional - size: PropTypes.number, - className: PropTypes.string, -}; - export default GravatarImg; diff --git a/packages/insomnia-app/app/ui/components/modals/ask-modal.js b/packages/insomnia-app/app/ui/components/modals/ask-modal.js index f81a84da1a..a941562b9f 100644 --- a/packages/insomnia-app/app/ui/components/modals/ask-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/ask-modal.js @@ -13,6 +13,8 @@ class AskModal extends PureComponent { this.state = { title: '', message: '', + yesText: 'Yes', + noText: 'No', }; } @@ -41,11 +43,16 @@ class AskModal extends PureComponent { } show(options = {}) { - const { title, message, onDone } = options; + const { title, message, onDone, yesText, noText } = options; this._doneCallback = onDone; - this.setState({ title, message }); + this.setState({ + title: title || 'Confirm', + message: message || 'No message provided', + yesText: yesText || 'Yes', + noText: noText || 'No', + }); this.modal.show(); @@ -59,7 +66,7 @@ class AskModal extends PureComponent { } render() { - const { message, title } = this.state; + const { message, title, yesText, noText } = this.state; return ( @@ -68,10 +75,10 @@ class AskModal extends PureComponent {
diff --git a/packages/insomnia-app/app/ui/components/modals/filter-help-modal.js b/packages/insomnia-app/app/ui/components/modals/filter-help-modal.js index b9eb48626f..d3885f3eb3 100644 --- a/packages/insomnia-app/app/ui/components/modals/filter-help-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/filter-help-modal.js @@ -78,6 +78,17 @@ class FilterHelpModal extends PureComponent { Get the number of books in the store + + {isJson && ( + + + + $.store.books[?(@.title.match(/lord.*rings/i))] + + + Get book by title regular expression + + )} diff --git a/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js b/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js index aacdebc28f..78fe2ecc9c 100644 --- a/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js @@ -75,6 +75,20 @@ class RequestSettingsModal extends React.PureComponent { this.setState({ request }); } + async _updateRequestSettingString(e: SyntheticEvent) { + if (!this.state.request) { + // Should never happen + return; + } + + const value = e.currentTarget.value; + const setting = e.currentTarget.name; + const request = await models.request.update(this.state.request, { + [setting]: value, + }); + this.setState({ request }); + } + async _handleNameChange(name: string) { if (!this.state.request) { return; @@ -304,6 +318,22 @@ class RequestSettingsModal extends React.PureComponent { {this.renderCheckboxInput('settingRebuildPath')}
+ +
+
+ +

diff --git a/packages/insomnia-app/app/ui/components/modals/response-debug-modal.js b/packages/insomnia-app/app/ui/components/modals/response-debug-modal.js index 793972d6fc..6842b9d606 100644 --- a/packages/insomnia-app/app/ui/components/modals/response-debug-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/response-debug-modal.js @@ -9,11 +9,14 @@ import * as models from '../../../models/index'; import type { Response } from '../../../models/response'; import type { Settings } from '../../../models/settings'; -type Props = { settings: Settings }; +type Props = {| + settings: Settings, +|}; -type State = { +type State = {| response: Response | null, -}; + title: string | null, +|}; @autobind class ResponseDebugModal extends React.PureComponent { @@ -24,6 +27,7 @@ class ResponseDebugModal extends React.PureComponent { this.state = { response: null, + title: '', }; } @@ -35,24 +39,28 @@ class ResponseDebugModal extends React.PureComponent { this.modal && this.modal.hide(); } - async show(options: { responseId?: string, response?: Response }) { + async show(options: { responseId?: string, response?: Response, title?: string }) { const response = options.response ? options.response : await models.response.getById(options.responseId || 'n/a'); - this.setState({ response }); + this.setState({ + response, + title: options.title || null, + }); + this.modal && this.modal.show(); } render() { const { settings } = this.props; - const { response } = this.state; + const { response, title } = this.state; return ( - OAuth 2 Response + {title || 'Response Timeline'} -
+
{response ? ( diff --git a/packages/insomnia-app/app/ui/components/modals/workspace-environments-edit-modal.js b/packages/insomnia-app/app/ui/components/modals/workspace-environments-edit-modal.js index 10fa01324f..f6e522fe96 100644 --- a/packages/insomnia-app/app/ui/components/modals/workspace-environments-edit-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/workspace-environments-edit-modal.js @@ -205,6 +205,12 @@ class WorkspaceEnvironmentsEditModal extends React.PureComponent { await this._load(workspace, environment); } + async _handleDuplicateEnvironment(environment: Environment) { + const { workspace } = this.state; + const newEnvironment = await models.environment.duplicate(environment); + this._load(workspace, newEnvironment); + } + async _handleDeleteEnvironment(environment: Environment) { const { rootEnvironment, workspace } = this.state; @@ -484,6 +490,13 @@ class WorkspaceEnvironmentsEditModal extends React.PureComponent { + + { } // Remove the search string (?foo=bar&...) from the Url - const url = request.url.replace(query, ''); + const url = request.url.replace(`?${query}`, ''); const parameters = [...request.parameters, ...deconstructQueryStringToParams(query)]; // Only update if url changed diff --git a/packages/insomnia-app/app/ui/components/settings/import-export.js b/packages/insomnia-app/app/ui/components/settings/import-export.js index d2cc2a1c18..d5c4060968 100644 --- a/packages/insomnia-app/app/ui/components/settings/import-export.js +++ b/packages/insomnia-app/app/ui/components/settings/import-export.js @@ -20,7 +20,12 @@ class ImportExport extends PureComponent { } render() { - const { handleImportFile, handleExportAll, handleShowExportRequestsModal } = this.props; + const { + handleImportFile, + handleImportClipBoard, + handleExportAll, + handleShowExportRequestsModal, + } = this.props; return (
@@ -64,6 +69,10 @@ class ImportExport extends PureComponent { From URL + + + From Clipboard +    @@ -78,6 +87,7 @@ class ImportExport extends PureComponent { ImportExport.propTypes = { handleImportFile: PropTypes.func.isRequired, + handleImportClipBoard: PropTypes.func.isRequired, handleImportUri: PropTypes.func.isRequired, handleExportAll: PropTypes.func.isRequired, handleShowExportRequestsModal: PropTypes.func.isRequired, diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js index 3f2fc7b054..0fdcdc6c42 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-request-row.js @@ -59,6 +59,15 @@ class SidebarRequestRow extends PureComponent { showModal(RequestSettingsModal, { request: this.props.request }); } + _getMethodOverrideHeaderValue() { + let header = this.props.request.headers.find( + h => h.name.toLowerCase() === 'x-http-method-override', + ); + if (!header || header.disabled) header = null; + else header = header.value; + return header; + } + setDragDirection(dragDirection) { if (dragDirection !== this.state.dragDirection) { this.setState({ dragDirection }); @@ -116,14 +125,22 @@ class SidebarRequestRow extends PureComponent { onClick={this._handleRequestActivate} onContextMenu={this._handleShowRequestActions}>
- + ( - + )} />
diff --git a/packages/insomnia-app/app/ui/components/tags/method-tag.js b/packages/insomnia-app/app/ui/components/tags/method-tag.js index ea058c7139..6d97b3e3d2 100644 --- a/packages/insomnia-app/app/ui/components/tags/method-tag.js +++ b/packages/insomnia-app/app/ui/components/tags/method-tag.js @@ -1,24 +1,31 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import * as constants from '../../../common/constants'; +import { HTTP_METHODS } from '../../../common/constants'; import * as util from '../../../common/misc'; class MethodTag extends PureComponent { render() { - const { method, fullNames } = this.props; + const { method, override, fullNames } = this.props; let methodName = method; + let overrideName = override; + if (!HTTP_METHODS.includes(override)) overrideName = null; if (!fullNames) { - if (method === constants.METHOD_DELETE || method === constants.METHOD_OPTIONS) { - methodName = method.slice(0, 3); - } else if (method.length > 4) { - methodName = util.removeVowels(method).slice(0, 4); - } + methodName = util.formatMethodName(method); + if (overrideName) overrideName = util.formatMethodName(override); } return ( -
- {methodName} +
+ {overrideName && ( +
+ {methodName} +
+ )} +
+ {overrideName || methodName} +
); } @@ -28,6 +35,7 @@ MethodTag.propTypes = { method: PropTypes.string.isRequired, // Optional + override: PropTypes.string, fullNames: PropTypes.bool, }; diff --git a/packages/insomnia-app/app/ui/components/templating/variable-editor.js b/packages/insomnia-app/app/ui/components/templating/variable-editor.js index f500d724e8..41750bac01 100644 --- a/packages/insomnia-app/app/ui/components/templating/variable-editor.js +++ b/packages/insomnia-app/app/ui/components/templating/variable-editor.js @@ -6,7 +6,7 @@ import autobind from 'autobind-decorator'; class VariableEditor extends PureComponent { constructor(props) { super(props); - + this.textAreaRef = React.createRef(); const inner = props.defaultValue.replace(/\s*}}$/, '').replace(/^{{\s*/, ''); this.state = { @@ -19,6 +19,11 @@ class VariableEditor extends PureComponent { componentDidMount() { this._update(this.state.value, true); + this._resize(); + } + + componentDidUpdate() { + this._resize(); } _handleChange(e) { @@ -26,6 +31,14 @@ class VariableEditor extends PureComponent { this._update(name); } + _resize() { + setTimeout(() => { + const element = this.textAreaRef.current; + element.style.cssText = 'height:auto'; + element.style.cssText = `height:${element.scrollHeight}px;overflow:hidden`; + }, 200); + } + _setSelectRef(n) { this._select = n; @@ -95,7 +108,7 @@ class VariableEditor extends PureComponent { {error ? (