mirror of
https://github.com/Kong/insomnia.git
synced 2026-01-21 04:18:53 -05:00
Add test and fix conflicts
This commit is contained in:
@@ -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
|
||||
@@ -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
18
.github/actions/build-linux/Dockerfile
vendored
Normal 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"]
|
||||
18
.github/actions/build-linux/entrypoint.sh
vendored
Normal file
18
.github/actions/build-linux/entrypoint.sh
vendored
Normal 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
|
||||
2
docker/install-dependencies.sh → .github/actions/build-linux/install-dependencies.sh
vendored
Executable file → Normal file
2
docker/install-dependencies.sh → .github/actions/build-linux/install-dependencies.sh
vendored
Executable file → Normal 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
19
.github/actions/build-snap/Dockerfile
vendored
Normal 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"]
|
||||
22
.github/actions/build-snap/entrypoint.sh
vendored
Normal file
22
.github/actions/build-snap/entrypoint.sh
vendored
Normal 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
|
||||
70
.github/actions/build-snap/install-dependencies.sh
vendored
Normal file
70
.github/actions/build-snap/install-dependencies.sh
vendored
Normal 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
85
.github/workflows/main.yml
vendored
Normal 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
|
||||
76
.travis.yml
76
.travis.yml
@@ -2,70 +2,22 @@ language: node_js
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
sudo: required
|
||||
dist: trusty
|
||||
env:
|
||||
- COMPOSE_SCRIPT=package_linux
|
||||
- DOCKER_COMPOSE_VERSION=1.20.1
|
||||
before_install:
|
||||
- sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
before_deploy:
|
||||
- python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);'
|
||||
- docker-compose build
|
||||
- docker-compose build $COMPOSE_SCRIPT
|
||||
- docker-compose run $COMPOSE_SCRIPT
|
||||
- os: linux
|
||||
sudo: required
|
||||
dist: trusty
|
||||
env:
|
||||
- COMPOSE_SCRIPT=package_snap
|
||||
- DOCKER_COMPOSE_VERSION=1.20.1
|
||||
before_install:
|
||||
- sudo rm /usr/local/bin/docker-compose
|
||||
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
- chmod +x docker-compose
|
||||
- sudo mv docker-compose /usr/local/bin
|
||||
before_deploy:
|
||||
- python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);'
|
||||
- docker-compose build $COMPOSE_SCRIPT
|
||||
- docker-compose run $COMPOSE_SCRIPT
|
||||
- os: osx
|
||||
env:
|
||||
- CSC_LINK=$MAC_CSC_LINK
|
||||
- CSC_KEY_PASSWORD=$MAC_CSC_KEY_PASSWORD
|
||||
before_deploy:
|
||||
- npm run app-package
|
||||
- os: osx # High Sierra for older libcurl compatibility
|
||||
osx_image: xcode10.1
|
||||
env:
|
||||
- CSC_LINK=$MAC_CSC_LINK
|
||||
- CSC_KEY_PASSWORD=$MAC_CSC_KEY_PASSWORD
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- build/node_modules
|
||||
- $HOME/.electron
|
||||
- $HOME/.cache
|
||||
- node_modules
|
||||
- build/node_modules
|
||||
- $HOME/.electron
|
||||
- $HOME/.cache
|
||||
|
||||
script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm run bootstrap
|
||||
- npm test
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GITHUB_TOKEN
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
file:
|
||||
- packages/insomnia-app/dist/**/*.zip
|
||||
- packages/insomnia-app/dist/**/*.dmg
|
||||
- packages/insomnia-app/dist/**/*.deb
|
||||
- packages/insomnia-app/dist/**/*.snap
|
||||
- packages/insomnia-app/dist/**/*.rpm
|
||||
- packages/insomnia-app/dist/**/*.AppImage
|
||||
- packages/insomnia-app/dist/**/*.tar.gz
|
||||
on:
|
||||
tags: true
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm run bootstrap
|
||||
- npm test
|
||||
- npm run app-release
|
||||
|
||||
@@ -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
|
||||
|
||||
10
README.md
10
README.md
@@ -57,6 +57,15 @@ npm test
|
||||
npm run app-start
|
||||
```
|
||||
|
||||
If you are on Linux and have problems, you may need to install `libfontconfig-dev`
|
||||
|
||||
```bash
|
||||
# Install libfontconfig-dev
|
||||
sudo apt-get install libfontconfig-dev
|
||||
```
|
||||
|
||||
If you are on Windows and have problems, you may need to install [Windows Build Tools](https://github.com/felixrieseberg/windows-build-tools)
|
||||
|
||||
</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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
135
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -18,6 +18,10 @@ export function getAppName() {
|
||||
return packageJSON.app.productName;
|
||||
}
|
||||
|
||||
export function getAppId() {
|
||||
return packageJSON.app.appId;
|
||||
}
|
||||
|
||||
export function getAppPlatform() {
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"settingEncodeUrl": true,
|
||||
"settingSendCookies": true,
|
||||
"settingStoreCookies": true,
|
||||
"settingFollowRedirects": "global",
|
||||
"url": "https://insomnia.rest",
|
||||
"_type": "request"
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
]),
|
||||
|
||||
@@ -52,6 +52,7 @@ describe('init()', () => {
|
||||
'setUrl',
|
||||
'settingDisableRenderRequestBody',
|
||||
'settingEncodeUrl',
|
||||
'settingFollowRedirects',
|
||||
'settingSendCookies',
|
||||
'settingStoreCookies',
|
||||
]);
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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://*;"
|
||||
|
||||
@@ -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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
<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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
2
packages/insomnia-app/flow-typed/mkdirp.js
vendored
2
packages/insomnia-app/flow-typed/mkdirp.js
vendored
@@ -2,6 +2,6 @@
|
||||
|
||||
declare module 'mkdirp' {
|
||||
declare module.exports: {
|
||||
sync: (path: string) => void,
|
||||
sync: (path: string) => string | null,
|
||||
};
|
||||
}
|
||||
|
||||
2
packages/insomnia-app/flow-typed/moment.js
vendored
2
packages/insomnia-app/flow-typed/moment.js
vendored
@@ -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' {
|
||||
|
||||
1412
packages/insomnia-app/package-lock.json
generated
1412
packages/insomnia-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
46
packages/insomnia-app/scripts/afterSignHook.js
Normal file
46
packages/insomnia-app/scripts/afterSignHook.js
Normal 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);
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
99
packages/insomnia-app/scripts/release.js
Normal file
99
packages/insomnia-app/scripts/release.js
Normal 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,
|
||||
});
|
||||
}
|
||||
2
packages/insomnia-cookies/package-lock.json
generated
2
packages/insomnia-cookies/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "insomnia-cookies",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.19",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
2
packages/insomnia-importers/package-lock.json
generated
2
packages/insomnia-importers/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "insomnia-importers",
|
||||
"version": "2.0.19",
|
||||
"version": "2.0.22",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
curl --compressed 'https://www.google.com/'
|
||||
@@ -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
Reference in New Issue
Block a user