Add test and fix conflicts

This commit is contained in:
Gregory Schier
2019-11-22 13:31:23 -05:00
161 changed files with 4734 additions and 2344 deletions

View File

@@ -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

View File

@@ -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": {

18
.github/actions/build-linux/Dockerfile vendored Normal file
View File

@@ -0,0 +1,18 @@
FROM ubuntu:14.04
LABEL "name"="Insomnia-Ubuntu-14"
LABEL "maintainer"="Gregory Schier <gschier1990@gmail.com>"
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"]

View File

@@ -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

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/sh
# Fail on any errors
set -e

19
.github/actions/build-snap/Dockerfile vendored Normal file
View File

@@ -0,0 +1,19 @@
FROM snapcore/snapcraft:stable
LABEL "name"="Insomnia-Ubuntu-16"
LABEL "maintainer"="Gregory Schier <gschier1990@gmail.com>"
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"]

View File

@@ -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

View File

@@ -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

85
.github/workflows/main.yml vendored Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
</details>
<details>
@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

135
package-lock.json generated
View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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);

View File

@@ -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 = [
{

View File

@@ -18,6 +18,10 @@ export function getAppName() {
return packageJSON.app.productName;
}
export function getAppId() {
return packageJSON.app.appId;
}
export function getAppPlatform() {
return process.platform;
}

View File

@@ -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<KeyCombination> = getPlatformKeyCombinations(hotKey);
if (keyCombs.length === 0) {
return '';
}
return constructKeyCombinationDisplay(keyCombs[0], mustUsePlus);
}

View File

@@ -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<void> {
export async function importUri(
getWorkspaceId: () => Promise<string | null>,
uri: string,
): Promise<{
source: string,
error: Error | null,
summary: { [string]: Array<BaseModel> },
}> {
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<string | null>,
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];

View File

@@ -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 = {};

View File

@@ -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,
},

View File

@@ -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<string | null> {
const params = [
{ name: 'v', value: getAppVersion() },
{ name: 'app', value: getAppId() },
{ name: 'channel', value: settings.updateChannel },
];

View File

@@ -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(),

View File

@@ -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);

View File

@@ -82,6 +82,20 @@ export function getById(id: string): Promise<Environment | null> {
return db.get(type, id);
}
export async function duplicate(environment: Environment): Promise<Environment> {
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<void> {
return db.remove(environment);
}

View File

@@ -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',
};
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);

View File

@@ -18,6 +18,8 @@ export default async function(
redirectUri: string = '',
scope: string = '',
state: string = '',
audience: string = '',
resource: string = '',
): Promise<Object> {
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<Object> {
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,

View File

@@ -15,6 +15,7 @@ export default async function(
username: string,
password: string,
scope: string = '',
audience: string = '',
): Promise<Object> {
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,

View File

@@ -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}`);
}

View File

@@ -30,6 +30,7 @@
"settingEncodeUrl": true,
"settingSendCookies": true,
"settingStoreCookies": true,
"settingFollowRedirects": "global",
"url": "https://insomnia.rest",
"_type": "request"
}

View File

@@ -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',
},
]),

View File

@@ -52,6 +52,7 @@ describe('init()', () => {
'setUrl',
'settingDisableRenderRequestBody',
'settingEncodeUrl',
'settingFollowRedirects',
'settingSendCookies',
'settingStoreCookies',
]);

View File

@@ -10,10 +10,10 @@ export function init(): { import: Object, export: Object } {
return {
import: {
async uri(uri: string, options: { workspaceId?: string } = {}): Promise<void> {
await importUri(options.workspaceId || null, uri);
await importUri(() => Promise.resolve(options.workspaceId || null), uri);
},
async raw(text: string, options: { workspaceId?: string } = {}): Promise<void> {
await importRaw(options.workspaceId || null, text);
await importRaw(() => Promise.resolve(options.workspaceId || null), text);
},
},
export: {

View File

@@ -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;

View File

@@ -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<Plugin> = null;
export async function init(): Promise<void> {
@@ -157,7 +144,7 @@ export async function getPlugins(force: boolean = false): Promise<Array<Plugin>>
// "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);

View File

@@ -63,7 +63,7 @@ async function _isInsomniaPlugin(lookupName: string): Promise<Object> {
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',

View File

@@ -2,7 +2,6 @@
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Insomnia</title>
<meta
http-equiv="Content-Security-Policy"
content="default-src * insomnia://*; img-src blob: data: * insomnia://*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src blob: data: mediastream: * insomnia://*;"

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View File

@@ -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) {

View File

@@ -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');
}
},
});

View File

@@ -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 = '<label><i class="fa fa-exclamation-triangle"></i></label>' + innerHTML;
el.innerHTML = '<label><i class="fa fa-exclamation-triangle"></i></label>' + cleanedStr;
} else {
el.innerHTML = '<label></label>' + innerHTML;
}

View File

@@ -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);
}

View File

@@ -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<Props> {
);
}
renderPastResponses(responses: Array<Response>) {
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 (
<React.Fragment>
<DropdownDivider>5 Minutes Ago</DropdownDivider>
{categories[0].map(this.renderDropdownItem)}
<DropdownDivider>2 Hours Ago</DropdownDivider>
{categories[1].map(this.renderDropdownItem)}
<DropdownDivider>Today</DropdownDivider>
{categories[2].map(this.renderDropdownItem)}
<DropdownDivider>This Week</DropdownDivider>
{categories[3].map(this.renderDropdownItem)}
</React.Fragment>
);
}
render() {
const {
activeResponse, // eslint-disable-line no-unused-vars
@@ -117,8 +153,7 @@ class ResponseHistoryDropdown extends React.PureComponent<Props> {
<i className="fa fa-trash-o" />
Clear History
</DropdownItem>
<DropdownDivider>Past Responses</DropdownDivider>
{responses.map(this.renderDropdownItem)}
{this.renderPastResponses(responses)}
</Dropdown>
</KeydownBinder>
);

View File

@@ -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<Props, State> {
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<Props, State> {
</DropdownItem>
))}
<DropdownDivider>Insomnia Version {getAppVersion()}</DropdownDivider>
<DropdownDivider>
{getAppName()} v{getAppVersion()}
</DropdownDivider>
<DropdownItem onClick={WorkspaceDropdown._handleShowSettings}>
<i className="fa fa-cog" /> Preferences

View File

@@ -425,7 +425,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
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<Props, State> {
} 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];

View File

@@ -227,7 +227,7 @@ class GraphQLEditor extends React.PureComponent<Props, State> {
const { response } = schemaFetchError;
showModal(ResponseDebugModal, {
title: 'Introspection Request',
title: 'GraphQL Introspection Response',
response: response,
});
}
@@ -345,8 +345,12 @@ class GraphQLEditor extends React.PureComponent<Props, State> {
});
}
async _handleRefreshSchema(): Promise<void> {
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<void> {
@@ -395,6 +399,20 @@ class GraphQLEditor extends React.PureComponent<Props, State> {
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<Props, State> {
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 {

View File

@@ -129,8 +129,8 @@ class RequestHeadersEditor extends React.PureComponent<Props> {
<div className="scrollable">
<KeyValueEditor
sortable
namePlaceholder="Header"
valuePlaceholder="Value"
namePlaceholder="header"
valuePlaceholder="value"
pairs={request.headers}
nunjucksPowerUserMode={nunjucksPowerUserMode}
isVariableUncovered={isVariableUncovered}

View File

@@ -0,0 +1,58 @@
// @flow
import * as React from 'react';
import autobind from 'autobind-decorator';
import MarkdownPreview from '../markdown-preview';
import type { GraphQLEnumValue } from 'graphql';
import { GraphQLEnumType } from 'graphql';
type Props = {|
type: GraphQLEnumType,
|};
@autobind
class GraphQLExplorerEnum extends React.PureComponent<Props> {
renderDescription() {
const { type } = this.props;
return <MarkdownPreview markdown={type.description || '*no description*'} />;
}
renderValues() {
const { type } = this.props;
const values = type.getValues();
return (
<React.Fragment>
<h2 className="graphql-explorer__subheading">Values</h2>
<ul className="graphql-explorer__defs">
{values.map((value: GraphQLEnumValue) => {
const description =
value.description ||
'This is a long paragraph that is a description for the enum value ' + value.name;
return (
<li key={value.name}>
<span className="selectable bold">{value.name}</span>
{description && (
<div className="graphql-explorer__defs__description">
<MarkdownPreview markdown={description} />
</div>
)}
</li>
);
})}
</ul>
</React.Fragment>
);
}
render() {
return (
<div className="graphql-explorer__type">
{this.renderDescription()}
{this.renderValues()}
</div>
);
}
}
export default GraphQLExplorerEnum;

View File

@@ -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<any, any> | null,
},
};
type HistoryItem = {
currentType: null | GraphQLType,
currentType: null | GraphQLType | GraphQLEnumType,
currentField: null | GraphQLField<any, any>,
};
@@ -37,7 +39,7 @@ class GraphQLExplorer extends React.PureComponent<Props, State> {
};
}
_handleNavigateType(type: GraphQLType) {
_handleNavigateType(type: GraphQLType | GraphQLEnumType) {
this.setState({
currentType: type,
currentField: null,
@@ -160,6 +162,8 @@ class GraphQLExplorer extends React.PureComponent<Props, State> {
child = (
<GraphQLExplorerField onNavigateType={this._handleNavigateType} field={currentField} />
);
} else if (currentType && currentType instanceof GraphQLEnumType) {
child = <GraphQLExplorerEnum type={currentType} />;
} else if (currentType) {
child = (
<GraphQLExplorerType

View File

@@ -1,37 +1,39 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
// @flow
import * as React from 'react';
import crypto from 'crypto';
class GravatarImg extends PureComponent {
type Props = {|
email?: string,
size?: number,
fallback?: string,
className?: string,
|};
class GravatarImg extends React.PureComponent<Props> {
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 (
<img
src={url}
src={src}
alt="Profile picture"
title="Profile picture"
className={className}
title={sanitizedEmail}
style={{ width: cssSize, height: cssSize }}
/>
);
}
}
GravatarImg.propTypes = {
// Required
email: PropTypes.string.isRequired,
// Optional
size: PropTypes.number,
className: PropTypes.string,
};
export default GravatarImg;

View File

@@ -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 (
<Modal noEscape ref={this._setModalRef} closeOnKeyCodes={[13]}>
@@ -68,10 +75,10 @@ class AskModal extends PureComponent {
<ModalFooter>
<div>
<button className="btn" onClick={this._handleNo}>
No
{noText}
</button>
<button ref={this._setYesButtonRef} className="btn" onClick={this._handleYes}>
Yes
{yesText}
</button>
</div>
</ModalFooter>

View File

@@ -78,6 +78,17 @@ class FilterHelpModal extends PureComponent {
</td>
<td>Get the number of books in the store</td>
</tr>
{isJson && (
<tr>
<td>
<code className="selectable">
$.store.books[?(@.title.match(/lord.*rings/i))]
</code>
</td>
<td>Get book by title regular expression</td>
</tr>
)}
</tbody>
</table>
</ModalBody>

View File

@@ -75,6 +75,20 @@ class RequestSettingsModal extends React.PureComponent<Props, State> {
this.setState({ request });
}
async _updateRequestSettingString(e: SyntheticEvent<HTMLInputElement>) {
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<Props, State> {
{this.renderCheckboxInput('settingRebuildPath')}
</label>
</div>
</div>
<div className="pad-top">
<div className="form-control form-control--outlined">
<label>
Follow redirects{' '}
<span className="txt-sm faint italic">(overrides global setting)</span>
<select
defaultValue={this.state.request && this.state.request.settingFollowRedirects}
name="settingFollowRedirects"
onChange={this._updateRequestSettingString}>
<option value={'global'}>Use global setting</option>
<option value={'off'}>Don't follow redirects</option>
<option value={'on'}>Follow redirects</option>
</select>
</label>
</div>
<hr />
<div className="form-row">
<div className="form-control form-control--outlined">

View File

@@ -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<Props, State> {
@@ -24,6 +27,7 @@ class ResponseDebugModal extends React.PureComponent<Props, State> {
this.state = {
response: null,
title: '',
};
}
@@ -35,24 +39,28 @@ class ResponseDebugModal extends React.PureComponent<Props, State> {
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 (
<Modal ref={this._setModalRef} tall>
<ModalHeader>OAuth 2 Response</ModalHeader>
<ModalHeader>{title || 'Response Timeline'}</ModalHeader>
<ModalBody>
<div style={{ display: 'grid' }} className="tall pad-top">
<div style={{ display: 'grid' }} className="tall">
{response ? (
<ResponseTimelineViewer
editorFontSize={settings.editorFontSize}

View File

@@ -53,6 +53,11 @@ class SettingsModal extends PureComponent {
this.modal.hide();
}
_handleImportClipBoard() {
this.props.handleImportClipBoard();
this.modal.hide();
}
_handleImportUri(uri) {
this.props.handleImportUri(uri);
this.modal.hide();
@@ -134,6 +139,7 @@ class SettingsModal extends PureComponent {
handleExportAll={this._handleExportAllToFile}
handleShowExportRequestsModal={this._handleShowExportRequestsModal}
handleImportFile={this._handleImportFile}
handleImportClipBoard={this._handleImportClipBoard}
handleImportUri={this._handleImportUri}
/>
</TabPanel>

View File

@@ -205,6 +205,12 @@ class WorkspaceEnvironmentsEditModal extends React.PureComponent<Props, State> {
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<Props, State> {
</DropdownItem>
</Dropdown>
<Button
value={activeEnvironment}
onClick={this._handleDuplicateEnvironment}
className="btn btn--clicky space-right">
<i className="fa fa-copy" /> Duplicate
</Button>
<PromptButton
value={activeEnvironment}
onClick={this._handleDeleteEnvironment}

View File

@@ -9,28 +9,28 @@ import type {
import type { Workspace } from '../../models/workspace';
import type { OAuth2Token } from '../../models/o-auth-2-token';
import * as React from 'react';
import autobind from 'autobind-decorator';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import ContentTypeDropdown from './dropdowns/content-type-dropdown';
import AuthDropdown from './dropdowns/auth-dropdown';
import KeyValueEditor from './key-value-editor/editor';
import RequestHeadersEditor from './editors/request-headers-editor';
import RenderedQueryString from './rendered-query-string';
import BodyEditor from './editors/body/body-editor';
import AuthWrapper from './editors/auth/auth-wrapper';
import RequestUrlBar from './request-url-bar.js';
import { getAuthTypeName, getContentTypeName } from '../../common/constants';
import { deconstructQueryStringToParams, extractQueryStringFromUrl } from 'insomnia-url';
import * as React from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import { getAuthTypeName, getContentTypeName } from '../../common/constants';
import * as db from '../../common/database';
import { hotKeyRefs } from '../../common/hotkeys';
import * as models from '../../models';
import AuthDropdown from './dropdowns/auth-dropdown';
import ContentTypeDropdown from './dropdowns/content-type-dropdown';
import AuthWrapper from './editors/auth/auth-wrapper';
import BodyEditor from './editors/body/body-editor';
import RequestHeadersEditor from './editors/request-headers-editor';
import ErrorBoundary from './error-boundary';
import Hotkey from './hotkey';
import KeyValueEditor from './key-value-editor/editor';
import MarkdownPreview from './markdown-preview';
import { showModal } from './modals/index';
import RequestSettingsModal from './modals/request-settings-modal';
import MarkdownPreview from './markdown-preview';
import RenderedQueryString from './rendered-query-string';
import RequestUrlBar from './request-url-bar.js';
import type { Settings } from '../../models/settings';
import ErrorBoundary from './error-boundary';
import { hotKeyRefs } from '../../common/hotkeys';
type Props = {
// Functions
@@ -136,7 +136,7 @@ class RequestPane extends React.PureComponent<Props> {
}
// 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

View File

@@ -20,7 +20,12 @@ class ImportExport extends PureComponent {
}
render() {
const { handleImportFile, handleExportAll, handleShowExportRequestsModal } = this.props;
const {
handleImportFile,
handleImportClipBoard,
handleExportAll,
handleShowExportRequestsModal,
} = this.props;
return (
<div>
@@ -64,6 +69,10 @@ class ImportExport extends PureComponent {
<i className="fa fa-link" />
From URL
</DropdownItem>
<DropdownItem onClick={handleImportClipBoard}>
<i className="fa fa-clipboard" />
From Clipboard
</DropdownItem>
</Dropdown>
&nbsp;&nbsp;
<Link href="https://insomnia.rest/create-run-button/" className="btn btn--compact" button>
@@ -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,

View File

@@ -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}>
<div className="sidebar__clickable">
<MethodTag method={request.method} />
<MethodTag
method={request.method}
override={this._getMethodOverrideHeaderValue()}
/>
<Editable
value={request.name}
className="inline-block"
onEditStart={this._handleEditStart}
onSubmit={this._handleRequestUpdateName}
renderReadView={(value, props) => (
<Highlight search={filter} text={value} {...props} />
<Highlight
search={filter}
text={value}
{...props}
title={`${request.name}\n${props.title}`}
/>
)}
/>
</div>

View File

@@ -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 (
<div className={'tag tag--no-bg tag--small http-method-' + method}>
<span className="tag__inner">{methodName}</span>
<div style={{ position: 'relative' }}>
{overrideName && (
<div className={'tag tag--no-bg tag--superscript http-method-' + method}>
<span>{methodName}</span>
</div>
)}
<div
className={'tag tag--no-bg tag--small http-method-' + (overrideName ? override : method)}>
<span className="tag__inner">{overrideName || methodName}</span>
</div>
</div>
);
}
@@ -28,6 +35,7 @@ MethodTag.propTypes = {
method: PropTypes.string.isRequired,
// Optional
override: PropTypes.string,
fullNames: PropTypes.bool,
};

View File

@@ -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 ? (
<textarea className="danger" value={error || 'Error'} readOnly />
) : (
<textarea value={preview || ''} readOnly />
<textarea ref={this.textAreaRef} value={preview || ''} readOnly />
)}
</label>
</div>

View File

@@ -80,6 +80,7 @@ class Toast extends React.PureComponent<Props, State> {
firstLaunch: stats.created,
launches: stats.launches,
platform: constants.getAppPlatform(),
app: constants.getAppId(),
version: constants.getAppVersion(),
requests: await db.count(models.request.type),
requestGroups: await db.count(models.requestGroup.type),

View File

@@ -73,6 +73,7 @@ type Props = {
handleSetSidebarFilter: Function,
handleToggleMenuBar: Function,
handleImportFileToWorkspace: Function,
handleImportClipBoardToWorkspace: Function,
handleImportUriToWorkspace: Function,
handleExportFile: Function,
handleShowExportRequestsModal: Function,
@@ -268,6 +269,10 @@ class Wrapper extends React.PureComponent<Props, State> {
this.props.handleImportFileToWorkspace(this.props.activeWorkspace._id);
}
_handleImportClipBoard(): void {
this.props.handleImportClipBoardToWorkspace(this.props.activeWorkspace._id);
}
_handleImportUri(uri: string): void {
this.props.handleImportUriToWorkspace(this.props.activeWorkspace._id, uri);
}
@@ -638,6 +643,7 @@ class Wrapper extends React.PureComponent<Props, State> {
handleShowExportRequestsModal={handleShowExportRequestsModal}
handleExportAllToFile={handleExportFile}
handleImportFile={this._handleImportFile}
handleImportClipBoard={this._handleImportClipBoard}
handleImportUri={this._handleImportUri}
handleToggleMenuBar={handleToggleMenuBar}
settings={settings}

View File

@@ -1269,6 +1269,7 @@ function mapDispatchToProps(dispatch) {
handleSetActiveWorkspace: global.setActiveWorkspace,
handleImportFileToWorkspace: global.importFile,
handleImportClipBoardToWorkspace: global.importClipBoard,
handleImportUriToWorkspace: global.importUri,
handleCommand: global.newCommand,
handleExportFile: global.exportWorkspacesToFile,

View File

@@ -14,6 +14,7 @@
z-index: 100;
background: var(--color-bg);
box-shadow: 0 0 1rem 0 rgba(0, 0, 0, 0.4);
color: var(--color-font);
.markdown-preview__content *:first-child {
margin-top: @padding-sm;

View File

@@ -12,6 +12,13 @@
border: 1px solid rgba(0, 0, 0, 0.07);
white-space: nowrap;
&.tag--superscript {
position: absolute;
font-size: 0.6em;
bottom: 0.9em;
left: -0.6em;
}
&:last-child {
margin-right: 0;
}

View File

@@ -71,7 +71,6 @@
.CodeMirror,
.cm-s-seti.CodeMirror, // Hack because seti theme is dumb
.CodeMirror-gutters,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
// Let the background behind show through
@@ -98,7 +97,7 @@
}
.CodeMirror-gutters {
background-color: transparent;
background-color: var(--color-bg);
}
.CodeMirror-scroll {

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import * as packageJson from '../../package.json';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/app';
@@ -16,6 +17,7 @@ import DNDBackend from './dnd-backend';
// Handy little helper
document.body.setAttribute('data-platform', process.platform);
document.title = packageJson.app.longName;
(async function() {
await db.initClient();

View File

@@ -1,15 +1,35 @@
import clone from 'clone';
import * as db from '../../../common/database';
import * as models from '../../../models';
const ENTITY_CHANGES = 'entities/changes';
const ENTITY_INITIALIZE = 'entities/initialize';
// ~~~~~~~~ //
// Reducers //
// ~~~~~~~~ //
function getReducerName(type) {
const trailer = type.match(/s$/) ? '' : 's';
return `${type.slice(0, 1).toLowerCase()}${type.slice(1)}${trailer}`;
let trailer = 's';
let chop = 0;
// Things already ending with 's' stay that way
if (type.match(/s$/)) {
trailer = '';
chop = 0;
}
// Things ending in 'y' convert to ies
if (type.match(/y$/)) {
trailer = 'ies';
chop = 1;
}
// Lowercase first letter (camel case)
const lowerFirstLetter = `${type.slice(0, 1).toLowerCase()}${type.slice(1)}`;
// Add the trailer for pluralization
return `${lowerFirstLetter.slice(0, lowerFirstLetter.length - chop)}${trailer}`;
}
const initialState = {};
@@ -20,8 +40,16 @@ for (const type of models.types()) {
export function reducer(state = initialState, action) {
switch (action.type) {
case ENTITY_INITIALIZE:
const freshState = clone(initialState);
const { docs } = action;
for (const doc of docs) {
const referenceName = getReducerName(doc.type);
freshState[referenceName][doc._id] = doc;
}
return freshState;
case ENTITY_CHANGES:
const newState = Object.assign({}, state);
const newState = clone(state);
const { changes } = action;
for (const [event, doc] of changes) {
@@ -63,3 +91,33 @@ export function addChanges(changes) {
export function addChangesSync(changes) {
return { type: ENTITY_CHANGES, changes };
}
export function initialize() {
return async dispatch => {
const docs = await allDocs();
dispatch(initializeWith(docs));
};
}
export function initializeWith(docs) {
return { type: ENTITY_INITIALIZE, docs };
}
export async function allDocs() {
// NOTE: This list should be from most to least specific (ie. parents above children)
return [
...(await models.settings.all()),
...(await models.workspace.all()),
...(await models.workspaceMeta.all()),
...(await models.environment.all()),
...(await models.cookieJar.all()),
...(await models.requestGroup.all()),
...(await models.requestGroupMeta.all()),
...(await models.request.all()),
...(await models.requestMeta.all()),
...(await models.requestVersion.all()),
...(await models.response.all()),
...(await models.oAuth2Token.all()),
...(await models.clientCertificate.all()),
];
}

View File

@@ -132,10 +132,8 @@ export function loadRequestStop(requestId) {
}
export function setActiveWorkspace(workspaceId) {
window.localStorage.setItem(
`${LOCALSTORAGE_PREFIX}::activeWorkspaceId`,
JSON.stringify(workspaceId),
);
const key = `${LOCALSTORAGE_PREFIX}::activeWorkspaceId`;
window.localStorage.setItem(key, JSON.stringify(workspaceId));
return { type: SET_ACTIVE_WORKSPACE, workspaceId };
}
@@ -163,30 +161,63 @@ export function importFile(workspaceId) {
}
// Let's import all the paths!
let importedWorkspaces = [];
for (const p of paths) {
try {
const uri = `file://${p}`;
await importUtils.importUri(workspaceId, uri);
const result = await importUtils.importUri(askToImportIntoWorkspace(workspaceId), uri);
importedWorkspaces = [...importedWorkspaces, ...result.summary[models.workspace.type]];
} catch (err) {
showModal(AlertModal, { title: 'Import Failed', message: err + '' });
} finally {
dispatch(loadStop());
}
}
if (importedWorkspaces.length === 1) {
dispatch(setActiveWorkspace(importedWorkspaces[0]._id));
}
});
};
}
export function importClipBoard(workspaceId) {
return async dispatch => {
dispatch(loadStart());
const schema = electron.clipboard.readText();
// Let's import all the paths!
let importedWorkspaces = [];
try {
const result = await importUtils.importRaw(askToImportIntoWorkspace(workspaceId), schema);
importedWorkspaces = [...importedWorkspaces, ...result.summary[models.workspace.type]];
} catch (err) {
showModal(AlertModal, { title: 'Import Failed', message: err + '' });
} finally {
dispatch(loadStop());
}
if (importedWorkspaces.length === 1) {
dispatch(setActiveWorkspace(importedWorkspaces[0]._id));
}
};
}
export function importUri(workspaceId, uri) {
return async dispatch => {
dispatch(loadStart());
let importedWorkspaces = [];
try {
await importUtils.importUri(workspaceId, uri);
const result = await importUtils.importUri(askToImportIntoWorkspace(workspaceId), uri);
importedWorkspaces = [...importedWorkspaces, ...result.summary[models.workspace.type]];
} catch (err) {
showModal(AlertModal, { title: 'Import Failed', message: err + '' });
} finally {
dispatch(loadStop());
}
if (importedWorkspaces.length === 1) {
dispatch(setActiveWorkspace(importedWorkspaces[0]._id));
}
};
}
@@ -434,3 +465,23 @@ export function init() {
return setActiveWorkspace(workspaceId);
}
// ~~~~~~~ //
// HELPERS //
// ~~~~~~~ //
function askToImportIntoWorkspace(workspaceId) {
return function() {
return new Promise(resolve => {
showModal(AskModal, {
title: 'Import',
message: 'Do you want to import into the current workspace or a new one?',
yesText: 'Current',
noText: 'New Workspace',
onDone: yes => {
resolve(yes ? workspaceId : null);
},
});
});
};
}

View File

@@ -3,7 +3,6 @@ import * as entities from './entities';
import configureStore from '../create';
import * as global from './global';
import * as db from '../../../common/database';
import * as models from '../../../models';
import { API_BASE_URL, getClientString } from '../../../common/constants';
import { isLoggedIn, onLoginLogout } from '../../../account/session';
import * as fetch from '../../../account/fetch';
@@ -12,14 +11,13 @@ export async function init() {
const store = configureStore();
// Do things that must happen before initial render
const { addChanges, addChangesSync } = bindActionCreators(entities, store.dispatch);
const { addChanges, initializeWith: initEntities } = bindActionCreators(entities, store.dispatch);
const { newCommand, loginStateChange } = bindActionCreators(global, store.dispatch);
const allDocs = await getAllDocs();
// Link DB changes to entities reducer/actions
const changes = allDocs.map(doc => [db.CHANGE_UPDATE, doc]);
addChangesSync(changes);
const docs = await entities.allDocs();
initEntities(docs);
db.onChange(addChanges);
// Initialize login state
@@ -41,27 +39,3 @@ export const reducer = combineReducers({
entities: entities.reducer,
global: global.reducer,
});
/**
* Async function to get all docs concurrently
*/
async function getAllDocs() {
// Restore docs in parent->child->grandchild order
const allDocs = [
...(await models.settings.all()),
...(await models.workspace.all()),
...(await models.workspaceMeta.all()),
...(await models.environment.all()),
...(await models.cookieJar.all()),
...(await models.requestGroup.all()),
...(await models.requestGroupMeta.all()),
...(await models.request.all()),
...(await models.requestMeta.all()),
...(await models.requestVersion.all()),
...(await models.response.all()),
...(await models.oAuth2Token.all()),
...(await models.clientCertificate.all()),
];
return allDocs;
}

View File

@@ -2,6 +2,6 @@
declare module 'mkdirp' {
declare module.exports: {
sync: (path: string) => void,
sync: (path: string) => string | null,
};
}

View File

@@ -3,6 +3,8 @@
declare type moment = {
fromNow: () => string,
format: (fmt: string) => string,
diff: (date: any, fmt?: string, floating?: boolean) => number,
isSame: (date?: any, units?: ?string) => boolean,
};
declare module 'moment' {

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,31 @@
{
"private": true,
"version": "1.1.8",
"version": "1.1.11",
"name": "insomnia-app",
"app": {
"name": "insomnia",
"executableName": "insomnia",
"appId": "com.insomnia.app",
"productName": "Insomnia",
"longName": "Insomnia REST Client",
"version": "6.6.2",
"main": "main.min.js"
"synopsis": "A simple, beautiful, and free REST API client",
"icon": "https://github.com/getinsomnia/insomnia/blob/master/packages/insomnia-app/app/icons/icon.ico?raw=true",
"theme": "default",
"version": "7.0.3",
"main": "main.min.js",
"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"
]
},
"licence": "MIT",
"homepage": "https://insomnia.rest/",
@@ -28,6 +46,7 @@
"start": "concurrently --kill-others \"npm run start:dev-server\" \"npm run start:electron\"",
"build": "node ./scripts/build.js",
"package": "node ./scripts/package.js",
"release": "node ./scripts/release.js",
"bootstrap": "rimraf node_modules/fsevents && rimraf node_modules/graphql-language-service-interface/dist/*.flow && electron-rebuild -f -w insomnia-libcurl"
},
"dev": {
@@ -116,23 +135,23 @@
"html-entities": "^1.2.0",
"httpsnippet": "^1.19.1",
"iconv-lite": "^0.4.15",
"insomnia-cookies": "^0.0.17",
"insomnia-importers": "^2.0.20",
"insomnia-libcurl": "^0.0.28",
"insomnia-plugin-base64": "^1.0.10",
"insomnia-plugin-cookie-jar": "^1.0.15",
"insomnia-plugin-core-themes": "^1.0.9",
"insomnia-plugin-file": "^1.0.11",
"insomnia-plugin-hash": "^1.0.11",
"insomnia-plugin-jsonpath": "^1.0.18",
"insomnia-plugin-now": "^1.0.16",
"insomnia-plugin-prompt": "^1.1.15",
"insomnia-plugin-request": "^1.0.24",
"insomnia-plugin-response": "^1.0.24",
"insomnia-plugin-uuid": "^1.0.15",
"insomnia-prettify": "^0.1.11",
"insomnia-url": "^0.1.10",
"insomnia-xpath": "^1.0.14",
"insomnia-cookies": "^0.0.20",
"insomnia-importers": "^2.0.23",
"insomnia-libcurl": "^0.0.31",
"insomnia-plugin-base64": "^1.0.12",
"insomnia-plugin-cookie-jar": "^1.0.18",
"insomnia-plugin-core-themes": "^1.0.11",
"insomnia-plugin-file": "^1.0.13",
"insomnia-plugin-hash": "^1.0.13",
"insomnia-plugin-jsonpath": "^1.0.21",
"insomnia-plugin-now": "^1.0.19",
"insomnia-plugin-prompt": "^1.1.17",
"insomnia-plugin-request": "^1.0.27",
"insomnia-plugin-response": "^1.0.27",
"insomnia-plugin-uuid": "^1.0.18",
"insomnia-prettify": "^0.1.13",
"insomnia-url": "^0.1.12",
"insomnia-xpath": "^1.0.17",
"json-order": "^1.0.9",
"jsonlint": "^1.6.3",
"jsonpath": "^1.0.2",
@@ -168,14 +187,16 @@
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@octokit/rest": "^16.33.0",
"concurrently": "^3.5.0",
"cross-env": "^2.0.0",
"css-loader": "^2.1.1",
"electron": "^3.0.4",
"electron-builder": "^20.20.4",
"electron-builder-lib": "^20.20.4",
"electron-builder-squirrel-windows": "^20.20.4",
"electron-rebuild": "^1.8.4",
"electron-builder": "^21.2.0",
"electron-builder-squirrel-windows": "^21.2.0",
"electron-notarize": "^0.1.1",
"electron-rebuild": "^1.8.8",
"fast-glob": "^3.1.0",
"file-loader": "^3.0.1",
"less": "^3.8.1",
"less-loader": "^4.1.0",

View File

@@ -0,0 +1,46 @@
const fs = require('fs');
const path = require('path');
const electronNotarize = require('electron-notarize');
const packageJson = require('../package.json');
// See: https://medium.com/@TwitterArchiveEraser/notarize-electron-apps-7a5f988406db
module.exports = async function(params) {
// Only notarize the app on Mac OS only.
if (process.platform !== 'darwin') {
return;
}
// Same appId in electron-builder.
const { appId } = packageJson.app;
const appName = `${params.packager.appInfo.productFilename}.app`;
const appPath = path.join(params.appOutDir, appName);
if (!fs.existsSync(appPath)) {
throw new Error(`Cannot find application at: ${appName}`);
}
const args = {
appBundleId: appId,
appPath: appPath,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
};
if (!process.env.APPLE_ID) {
console.log('[aftersign] APPLE_ID env variable not set. Skipping notarization');
return;
}
if (!process.env.APPLE_ID_PASSWORD) {
console.log('[aftersign] APPLE_ID env variable not set. Skipping notarization');
return;
}
console.log(`[afterSign] Notarizing ${appName} (${appId})`);
try {
await electronNotarize.notarize(args);
} catch (err) {
console.error(err);
}
};

View File

@@ -1,3 +1,4 @@
const packageJson = require('../package.json');
const childProcess = require('child_process');
const webpack = require('webpack');
const rimraf = require('rimraf');
@@ -20,6 +21,11 @@ module.exports.start = async function() {
console.log('[build] npm: ' + childProcess.spawnSync('npm', ['--version']).stdout);
console.log('[build] node: ' + childProcess.spawnSync('node', ['--version']).stdout);
if (process.version.indexOf('v10.15') !== 0) {
console.log('[build] Node v10.15.x is required to build');
process.exit(1);
}
// Remove folders first
console.log('[build] Removing existing directories');
await emptyDir('../build');
@@ -48,11 +54,13 @@ module.exports.start = async function() {
async function buildWebpack(config) {
return new Promise((resolve, reject) => {
webpack(config, (err, stats) => {
const compiler = webpack(config);
compiler.run((err, stats) => {
if (err) {
reject(err);
} else if (stats.hasErrors()) {
reject(new Error('Failed to build webpack'));
console.log(stats.toJson().errors);
} else {
resolve();
}
@@ -90,9 +98,41 @@ async function copyFiles(relSource, relDest) {
}
async function install(relDir) {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
const prefix = path.resolve(__dirname, relDir);
// // Link all plugins
// const plugins = path.resolve(__dirname, `../../../plugins`);
// for (const dir of fs.readdirSync(plugins)) {
// if (dir.indexOf('.') === 0) {
// continue;
// }
//
// console.log(`[build] Linking plugin ${dir}`);
// const p = path.join(plugins, dir);
// childProcess.spawnSync('npm', ['link', p], {
// cwd: prefix,
// shell: true,
// });
// }
// // Link all packages
// const packages = path.resolve(__dirname, `../../../packages`);
// for (const dir of fs.readdirSync(packages)) {
// // Don't like ourselves
// if (dir === packageJson.name) {
// continue;
// }
//
// if (dir.indexOf('.') === 0) {
// continue;
// }
//
// console.log(`[build] Linking local package ${dir}`);
// const p = path.join(packages, dir);
// childProcess.spawnSync('npm', ['link', p], { cwd: prefix, shell: true });
// }
const p = childProcess.spawn('npm', ['install', '--production', '--no-optional'], {
cwd: prefix,
shell: true,
@@ -121,7 +161,7 @@ function generatePackageJson(relBasePkg, relOutPkg) {
const basePkg = JSON.parse(fs.readFileSync(basePath));
const appPkg = {
name: 'insomnia',
name: packageJson.app.name,
version: basePkg.app.version,
productName: basePkg.app.productName,
longName: basePkg.app.longName,

View File

@@ -1,3 +1,4 @@
const packageJson = require('../package.json');
const electronBuilder = require('electron-builder');
const path = require('path');
const rimraf = require('rimraf');
@@ -38,7 +39,18 @@ module.exports.start = async function() {
async function pkg(relConfigPath) {
const configPath = path.resolve(__dirname, relConfigPath);
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// Replace some things
const rawConfig = fs
.readFileSync(configPath, 'utf8')
.replace('__APP_ID__', packageJson.app.appId)
.replace('__ICON_URL__', packageJson.app.icon)
.replace('__EXECUTABLE_NAME__', packageJson.app.executableName)
.replace('__SYNOPSIS__', packageJson.app.synopsis);
// console.log(`[package] Using electron-builder config\n${rawConfig}`);
const config = JSON.parse(rawConfig);
const targetPlatform = PLATFORM_MAP[process.platform];
const target = process.env.BUILD_TARGETS

View File

@@ -0,0 +1,99 @@
const packageJson = require('../package.json');
const glob = require('fast-glob');
const fs = require('fs');
const path = require('path');
const packageTask = require('./package');
const buildTask = require('./build');
const Octokit = require('@octokit/rest');
// Configure Octokit
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
const GITHUB_ORG = 'getinsomnia';
const GITHUB_REPO = 'insomnia';
// Start package if ran from CLI
if (require.main === module) {
process.nextTick(async () => {
// First check if we need to publish (uses Git tags)
const gitRefStr = process.env.GITHUB_REF || process.env.TRAVIS_TAG;
const skipPublish = !gitRefStr || !gitRefStr.match(/v\d+\.\d+\.\d+(-(beta|alpha)\.\d+)?$/);
if (skipPublish) {
console.log(`[package] Not packaging for ref=${gitRefStr}`);
process.exit(0);
}
try {
await buildTask.start();
await packageTask.start();
await start();
} catch (err) {
console.log('[package] ERROR:', err);
process.exit(1);
}
});
}
async function start() {
const tagName = `v${packageJson.app.version}`;
console.log(`[release] Creating release ${tagName}`);
const globs = {
darwin: ['dist/**/*.zip', 'dist/**/*.dmg'],
win32: ['dist/squirrel-windows/*'],
linux: [
'dist/**/*.snap',
'dist/**/*.rpm',
'dist/**/*.deb',
'dist/**/*.AppImage',
'dist/**/*.tar.gz',
],
};
const paths = await glob(globs[process.platform]);
const { data } = await getOrCreateRelease(tagName);
for (const p of paths) {
const name = path.basename(p);
console.log(`[release] Uploading ${p}`);
await octokit.request({
method: 'POST',
url: 'https://uploads.github.com/repos/:owner/:repo/releases/:id/assets{?name,label}"',
id: data.id,
name: name,
owner: GITHUB_ORG,
repo: GITHUB_REPO,
headers: {
'Content-Type': 'application/octet-stream',
},
data: fs.readFileSync(p),
});
}
console.log(`[release] Release created ${data.url}`);
}
async function getOrCreateRelease(tagName) {
try {
return await octokit.repos.getReleaseByTag({
owner: GITHUB_ORG,
repo: GITHUB_REPO,
tag: tagName,
});
} catch (err) {
// Doesn't exist
}
return octokit.repos.createRelease({
owner: GITHUB_ORG,
repo: GITHUB_REPO,
tag_name: tagName,
name: tagName,
body: `Full changelog ⇒ https://insomnia.rest/changelog/${packageJson.app.version}`,
draft: false,
preRelease: true,
});
}

View File

@@ -1,6 +1,6 @@
{
"name": "insomnia-cookies",
"version": "0.0.16",
"version": "0.0.19",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "insomnia-cookies",
"version": "0.0.17",
"version": "0.0.20",
"author": "Gregory Schier <gschier1990@gmail.com>",
"description": "Cookie utilities",
"license": "MIT",
@@ -10,5 +10,6 @@
},
"dependencies": {
"tough-cookie": "^2.3.3"
}
},
"gitHead": "f325aef9ffe748b2d05562782852e83b16056210"
}

View File

@@ -1,6 +1,6 @@
{
"name": "insomnia-importers",
"version": "2.0.19",
"version": "2.0.22",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "insomnia-importers",
"version": "2.0.20",
"version": "2.0.23",
"author": "Gregory Schier <gschier1990@gmail.com>",
"description": "Various data importers for Insomnia",
"license": "MIT",
@@ -20,5 +20,6 @@
"shell-quote": "^1.6.1",
"swagger-parser": "^6.0.5",
"yaml": "^1.5.0"
}
},
"gitHead": "f325aef9ffe748b2d05562782852e83b16056210"
}

View File

@@ -16,7 +16,12 @@ describe('Fixtures', () => {
const prefix = input.replace(/-input\.[^.]+/, '');
const output = `${prefix}-output.json`;
if (prefix.startsWith('skip')) {
continue;
}
it(`Import ${name} ${input}`, async () => {
expect.assertions(5);
expect(typeof input).toBe('string');
expect(typeof output).toBe('string');
@@ -32,6 +37,16 @@ describe('Fixtures', () => {
expected.__export_date = results.data.__export_date;
expect(results.data).toEqual(expected);
const ids = new Set();
for (const r of results.data.resources) {
if (ids.has(r._id)) {
throw new Error(
'Export contained multiple duplicate IDs: ' + JSON.stringify(r, null, '\t'),
);
}
ids.add(r._id);
}
});
}
}

View File

@@ -32,8 +32,8 @@
"_id": "__REQ_3__",
"_type": "request",
"parentId": "__WORKSPACE_ID__",
"url": "https://insomnia.rest/",
"name": "https://insomnia.rest/",
"url": "https://insomnia.rest",
"name": "https://insomnia.rest",
"method": "GET",
"body": {},
"parameters": [],

View File

@@ -8,14 +8,14 @@
"_id": "__REQ_1__",
"_type": "request",
"parentId": "__WORKSPACE_ID__",
"url": "http://192.168.1.1:9200/executions/_search?pretty",
"name": "http://192.168.1.1:9200/executions/_search?pretty",
"url": "http://192.168.1.1:9200/executions/_search",
"name": "http://192.168.1.1:9200/executions/_search",
"method": "POST",
"body": {
"mimeType": "",
"text": "{\"query\":{\"match_all\":{}}}"
},
"parameters": [],
"parameters": [{ "name": "pretty", "disabled": false, "value": "" }],
"headers": [],
"authentication": {}
}

View File

@@ -0,0 +1 @@
curl --compressed 'https://www.google.com/'

View File

@@ -0,0 +1,20 @@
{
"_type": "export",
"__export_format": 4,
"__export_date": "2016-11-18T22:34:51.526Z",
"__export_source": "insomnia.importers:v0.1.0",
"resources": [
{
"_id": "__REQ_1__",
"_type": "request",
"parentId": "__WORKSPACE_ID__",
"url": "https://www.google.com",
"name": "https://www.google.com",
"method": "GET",
"body": {},
"parameters": [],
"headers": [],
"authentication": {}
}
]
}

Some files were not shown because too many files have changed in this diff Show More