diff --git a/web/.prettierrc b/.prettierrc
similarity index 100%
rename from web/.prettierrc
rename to .prettierrc
diff --git a/.travis.yml b/.travis.yml
index 9213aa88..c208106d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,3 +27,4 @@ script:
- make test-cli
- make test-api
- make test-web
+ - make test-jslib
diff --git a/Gopkg.lock b/Gopkg.lock
index bc8e5a53..d352c06b 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -90,14 +90,6 @@
revision = "9eb7a3d310e89e471c2cdf1ea3ec8d7fc1ab969c"
version = "v2.5.2"
-[[projects]]
- digest = "1:318f1c959a8a740366fce4b1e1eb2fd914036b4af58fbd0a003349b305f118ad"
- name = "github.com/golang/protobuf"
- packages = ["proto"]
- pruneopts = "UT"
- revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
- version = "v1.3.1"
-
[[projects]]
digest = "1:bfb6d8aee23cd9b2db8fa3760ca11d7a934d9a05993d5233406f1e6042e4c110"
name = "github.com/google/go-github"
@@ -114,14 +106,6 @@
revision = "44c6ddd0a2342c386950e880b658017258da92fc"
version = "v1.0.0"
-[[projects]]
- digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"
- name = "github.com/gorilla/context"
- packages = ["."]
- pruneopts = "UT"
- revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
- version = "v1.1.1"
-
[[projects]]
digest = "1:fc51ecee8f31d03436c1a0167eb1e383ad0a241d02272541853f3995374a08f1"
name = "github.com/gorilla/css"
@@ -138,22 +122,6 @@
revision = "ed099d42384823742bba0bf9a72b53b55c9e2e38"
version = "v1.7.2"
-[[projects]]
- digest = "1:e72d1ebb8d395cf9f346fd9cbc652e5ae222dd85e0ac842dc57f175abed6d195"
- name = "github.com/gorilla/securecookie"
- packages = ["."]
- pruneopts = "UT"
- revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983"
- version = "v1.1.1"
-
-[[projects]]
- digest = "1:e5bf52fd66a2e984b57b4c0f2c4ee024ed749a19886246240629998dc0cf31ce"
- name = "github.com/gorilla/sessions"
- packages = ["."]
- pruneopts = "UT"
- revision = "f57b7e2d29c6211d16ffa52a0998272f75799030"
- version = "v1.1.3"
-
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
@@ -214,17 +182,6 @@
revision = "bc6a3c0594130b1e34005880bc600b6d3f49fa7f"
version = "v1.1.1"
-[[projects]]
- digest = "1:1241b137a50b99f7f395e6d3d917cafa4330bd17c55dacef18bfa8d87707533a"
- name = "github.com/markbates/goth"
- packages = [
- ".",
- "gothic",
- ]
- pruneopts = "UT"
- revision = "3b8012093d951beedd026d120be1792db01a08f6"
- version = "v1.54.1"
-
[[projects]]
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
name = "github.com/mattn/go-colorable"
@@ -367,28 +324,15 @@
[[projects]]
branch = "master"
- digest = "1:1294ed02c7f91baa918f37b4cd7041b3f6ed39aa2ee9644fd39eb9cf6adb0066"
+ digest = "1:d7b978a787736537d0ad2f84c0e8b75b05c2febef0537198e28edf1cf2768afd"
name = "golang.org/x/net"
packages = [
- "context",
- "context/ctxhttp",
"html",
"html/atom",
]
pruneopts = "UT"
revision = "3b0461eec859c4b73bb64fdc8285971fd33e3938"
-[[projects]]
- branch = "master"
- digest = "1:8d1c112fb1679fa097e9a9255a786ee47383fa2549a3da71bcb1334a693ebcfe"
- name = "golang.org/x/oauth2"
- packages = [
- ".",
- "internal",
- ]
- pruneopts = "UT"
- revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33"
-
[[projects]]
branch = "master"
digest = "1:fe40fbf915905f8a2397b321b3f10190edbdf5d293f087d01d7eb3a6d1a4adca"
@@ -408,22 +352,6 @@
pruneopts = "UT"
revision = "9d24e82272b4f38b78bc8cff74fa936d31ccd8ef"
-[[projects]]
- digest = "1:6eb6e3b6d9fffb62958cf7f7d88dbbe1dd6839436b0802e194c590667a40412a"
- name = "google.golang.org/appengine"
- packages = [
- "internal",
- "internal/base",
- "internal/datastore",
- "internal/log",
- "internal/remote_api",
- "internal/urlfetch",
- "urlfetch",
- ]
- pruneopts = "UT"
- revision = "b2f4a3cf3c67576a2ee09e1fe62656a5086ce880"
- version = "v1.6.1"
-
[[projects]]
branch = "v3"
digest = "1:7388652e2215a3f45d341d58766ed58317971030eb1cbd75f005f96ace8e9196"
@@ -469,8 +397,6 @@
"github.com/jinzhu/gorm",
"github.com/joho/godotenv",
"github.com/lib/pq",
- "github.com/markbates/goth",
- "github.com/markbates/goth/gothic",
"github.com/mattn/go-sqlite3",
"github.com/pkg/errors",
"github.com/robfig/cron",
diff --git a/Makefile b/Makefile
index 5a19a1af..b7566daf 100644
--- a/Makefile
+++ b/Makefile
@@ -41,13 +41,17 @@ endif
ifeq ($(CI), true)
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && npm install --unsafe-perm=true)
+ @(cd ${GOPATH}/src/github.com/dnote/dnote/browser && npm install --unsafe-perm=true)
+ @(cd ${GOPATH}/src/github.com/dnote/dnote/jslib && npm install --unsafe-perm=true)
else
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && npm install)
+ @(cd ${GOPATH}/src/github.com/dnote/dnote/browser && npm install)
+ @(cd ${GOPATH}/src/github.com/dnote/dnote/jslib && npm install)
endif
.PHONY: install-js
## test
-test: test-cli test-api test-web
+test: test-cli test-api test-web test-jslib
.PHONY: test
test-cli:
@@ -62,9 +66,24 @@ test-api:
test-web:
@echo "==> running web test"
+
+ifeq ($(WATCH), true)
+ @(cd ${GOPATH}/src/github.com/dnote/dnote/web && npm run test:watch)
+else
@(cd ${GOPATH}/src/github.com/dnote/dnote/web && npm run test)
+endif
.PHONY: test-web
+test-jslib:
+ @echo "==> running jslib test"
+
+ifeq ($(WATCH), true)
+ @(cd ${GOPATH}/src/github.com/dnote/dnote/jslib && npm run test:watch)
+else
+ @(cd ${GOPATH}/src/github.com/dnote/dnote/jslib && npm run test)
+endif
+.PHONY: test-jslib
+
# development
dev-server:
@echo "==> running dev environment"
diff --git a/README.md b/README.md
index 42925a3b..827ab511 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,17 @@

=========================
-Dnote is a simple notebook for developers.
+Dnote is a simple personal knowledge base.
[](https://travis-ci.org/dnote/dnote)
## What is Dnote?
-Dnote is a lightweight notebook for writing technical notes and neatly organizing them into books. The main design goal is to **keep you focused** by providing a way of swiftly capturing new information **without having to switch environment**. To that end, you can use Dnote as a command line interface, browser extension, web client, or an IDE plugin.
+Dnote is a lightweight personal knowledge base. The main design goal is to **keep you focused** by providing a way of swiftly capturing new information **without having to switch environment**. To that end, you can use Dnote as a command line interface, browser extension, web client, or an IDE plugin.
-It also offers **end-to-end encrypted** backup with AES-256, a seamless **multi device sync**, and **automated spaced repetition** to retain your memory in case you are building a personal knowledge base.
+It also offers a seamless **multi device sync**, and **automated spaced repetition** to retain your memory in case you are building a personal knowledge base.
-For more details, see the [download page](https://dnote.io/download) and [features](https://dnote.io/pricing).
+For more details, see the [download page](https://www.getdnote.com/download) and [features](https://www.getdnote.com/pricing).

@@ -56,10 +56,10 @@ Dnote is great for building a personal knowledge base because:
You can read more in the following user stories:
-- [How I Built a Personal Knowledge Base for Myself](https://dnote.io/blog/how-i-built-personal-knowledge-base-for-myself/)
-- [I Wrote Down Everything I Learned While Programming for a Month](https://dnote.io/blog/writing-everything-i-learn-coding-for-a-month/)
+- [How I Built a Personal Knowledge Base for Myself](https://www.getdnote.com/blog/how-i-built-personal-knowledge-base-for-myself/)
+- [I Wrote Down Everything I Learned While Programming for a Month](https://www.getdnote.com/blog/writing-everything-i-learn-coding-for-a-month/)
## See Also
-- [Homepage](https://dnote.io)
-- [Forum](https://forum.dnote.io)
+- [Homepage](https://www.getdnote.com)
+- [Forum](https://forum.getdnote.com)
diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md
index d2fffe73..00f925ae 100644
--- a/SELF_HOSTING.md
+++ b/SELF_HOSTING.md
@@ -135,7 +135,7 @@ The following is an example configuration:
```yaml
editor: nvim
-apiEndpoint: https://api.dnote.io
+apiEndpoint: https://api.getdnote.com
```
Simply change the value for `apiEndpoint` to a full URL to the self-hosted instance, followed by '/api', and save the configuration file.
diff --git a/browser/.eslintrc b/browser/.eslintrc
new file mode 100644
index 00000000..23fadc75
--- /dev/null
+++ b/browser/.eslintrc
@@ -0,0 +1,16 @@
+{ "extends": ["eslint-config-airbnb"],
+ "env": {
+ "browser": true,
+ "node": true,
+ "mocha": true
+ },
+ "parser": "@typescript-eslint/parser",
+ "rules": {
+ "@typescript-eslint/no-unused-vars": 1,
+ "react-hooks/rules-of-hooks": "error",
+ "react-hooks/exhaustive-deps": "warn",
+ },
+ "plugins": [
+ "react-hooks", "@typescript-eslint"
+ ],
+}
diff --git a/browser/.gitignore b/browser/.gitignore
new file mode 100644
index 00000000..b6da8558
--- /dev/null
+++ b/browser/.gitignore
@@ -0,0 +1,5 @@
+/dist
+/package
+/node_modules
+.DS_Store
+extension.tar.gz
diff --git a/browser/CONTRIBUTING.md b/browser/CONTRIBUTING.md
new file mode 100644
index 00000000..6c8f2355
--- /dev/null
+++ b/browser/CONTRIBUTING.md
@@ -0,0 +1,18 @@
+# Contributing
+
+Use the following commands to set up, build, and release.
+
+## Set up
+
+* `npm install` to install dependencies.
+
+## Developing locally
+
+* `npm run watch:firefox`
+* `npm run watch:chrome`
+
+## Releasing
+
+* Set a new version in `package.json`
+* Run `./scripts/build_prod.sh`
+ * A gulp task `manifest` will copy the version from `package.json` to `manifest.json`
diff --git a/browser/NOTE_TO_REVIEWER.md b/browser/NOTE_TO_REVIEWER.md
new file mode 100644
index 00000000..ada384f5
--- /dev/null
+++ b/browser/NOTE_TO_REVIEWER.md
@@ -0,0 +1,18 @@
+# Note to reviewer
+
+This document contains instructions about how to reproduce the final build of this extension.
+
+All releases are tagged and pushed to [the GitHub repository](https://github.com/dnote/dnote).
+
+## Steps
+
+To reproduce the obfuscated code for Firefox, please follow the steps below.
+
+1. Run `npm install` to install dependencies
+2. Run `./scripts/build_prod.sh` to build for Firefox and Chrome.
+
+The obfuscated code will be under `/dist/firefox` and `/dist/chrome`.
+
+## Further questions
+
+Please contact sung@dnote.io
diff --git a/browser/README.md b/browser/README.md
new file mode 100644
index 00000000..3dece9af
--- /dev/null
+++ b/browser/README.md
@@ -0,0 +1,28 @@
+# Dnote Browser Extension
+
+Dnote browser extension for Chrome and Firefox. Capture new information without opening a new tab or leaving your browser.
+
+
+
+## Installation
+
+1. Install the extension
+
+* Firefox - https://addons.mozilla.org/addon/dnote
+* Chrome - https://chrome.google.com/webstore/detail/dnote/mcfbfmihbijfaambfbbfcdcfibcjcahi
+
+2. Login with your API key from https://dnote.io
+
+## Overview
+
+We learn many things while reading technical articles, or browsing StackOverflow. Unless we write them down we forget most of them exponentially.
+
+This extension integrates seamlessly with [Dnote CLI](https://github.com/dnote/dnote/cli) and requires [Dnote Cloud](https://www.getdnote.com/pricing) account.
+
+## Hotkeys
+
+Write new notes without even moving your hands to the mouse.
+
+* **Ctrl + d** - Open the extension (**Ctrl + Shift + v** on Firefox on Linux).
+* **Shift + Enter** - Save the current note
+* **b** - Open the saved note in the browser
diff --git a/browser/assets/demo.gif b/browser/assets/demo.gif
new file mode 100644
index 00000000..b122a441
Binary files /dev/null and b/browser/assets/demo.gif differ
diff --git a/browser/gulpfile.js b/browser/gulpfile.js
new file mode 100644
index 00000000..6e873300
--- /dev/null
+++ b/browser/gulpfile.js
@@ -0,0 +1,79 @@
+const gulp = require('gulp');
+const del = require('del');
+const replace = require('gulp-replace');
+const gulpif = require('gulp-if');
+const imagemin = require('gulp-imagemin');
+const livereload = require('gulp-livereload');
+const zip = require('gulp-zip');
+
+const target = process.env.TARGET;
+
+gulp.task('manifest', () => {
+ const pkg = require('./package.json');
+
+ return gulp
+ .src(`manifests/${target}/manifest.json`)
+ .pipe(replace('__VERSION__', pkg.version))
+ .pipe(gulp.dest(`dist/${target}`));
+});
+
+gulp.task('styles', () => {
+ return gulp.src('src/styles/*.css').pipe(gulp.dest(`dist/${target}/styles`));
+});
+
+gulp.task(
+ 'html',
+ gulp.series('styles', () => {
+ return gulp.src('src/*.html').pipe(gulp.dest(`dist/${target}`));
+ })
+);
+
+gulp.task('images', () => {
+ return gulp
+ .src('src/images/**/*')
+ .pipe(
+ gulpif(
+ gulpif.isFile,
+ imagemin({
+ progressive: true,
+ interlaced: true,
+ svgoPlugins: [{ cleanupIDs: false }]
+ })
+ )
+ )
+ .pipe(gulp.dest(`dist/${target}/images`));
+});
+
+gulp.task('clean', del.bind(null, ['.tmp', `dist/${target}`]));
+
+gulp.task(
+ 'watch',
+ gulp.series('manifest', 'html', 'styles', 'images', () => {
+ livereload.listen();
+
+ gulp
+ .watch([
+ 'src/*.html',
+ 'src/scripts/**/*',
+ 'src/images/**/*',
+ 'src/styles/**/*'
+ ])
+ .on('change', livereload.reload);
+
+ gulp.watch('src/*.html', gulp.parallel('html'));
+ gulp.watch('manifests/**/*.json', gulp.parallel('manifest'));
+ })
+);
+
+gulp.task('package', function() {
+ const manifest = require(`./dist/${target}/manifest.json`);
+
+ return gulp
+ .src(`dist/${target}/**`)
+ .pipe(zip('dnote-' + manifest.version + '.zip'))
+ .pipe(gulp.dest(`package/${target}`));
+});
+
+gulp.task('build', gulp.series('manifest', gulp.parallel('html', 'images')));
+
+gulp.task('default', gulp.series('clean', 'build'));
diff --git a/browser/manifests/chrome/manifest.json b/browser/manifests/chrome/manifest.json
new file mode 100644
index 00000000..4b95c5dc
--- /dev/null
+++ b/browser/manifests/chrome/manifest.json
@@ -0,0 +1,31 @@
+{
+ "name": "Dnote",
+ "version": "__VERSION__",
+ "description": "Capture your microlessons without leaving the browser.",
+ "icons": {
+ "16": "images/iconx16.png",
+ "48": "images/iconx48.png",
+ "128": "images/iconx128.png"
+ },
+ "manifest_version": 2,
+ "browser_action": {
+ "default_icon": {
+ "16": "images/iconx16.png",
+ "32": "images/iconx32.png"
+ },
+ "default_popup": "popup.html"
+ },
+ "background": {
+ "scripts": []
+ },
+ "content_scripts": [],
+ "permissions": ["storage"],
+ "commands": {
+ "_execute_browser_action": {
+ "suggested_key": {
+ "default": "Ctrl+D",
+ "mac": "MacCtrl+D"
+ }
+ }
+ }
+}
diff --git a/browser/manifests/firefox/manifest.json b/browser/manifests/firefox/manifest.json
new file mode 100644
index 00000000..8175011f
--- /dev/null
+++ b/browser/manifests/firefox/manifest.json
@@ -0,0 +1,38 @@
+{
+ "name": "Dnote",
+ "version": "__VERSION__",
+ "description": "Capture your microlessons without leaving the browser.",
+ "applications": {
+ "gecko": {
+ "id": "sung@dnote.io",
+ "strict_min_version": "42.0"
+ }
+ },
+ "icons": {
+ "16": "images/iconx16.png",
+ "48": "images/iconx48.png",
+ "128": "images/iconx128.png"
+ },
+ "manifest_version": 2,
+ "browser_action": {
+ "default_icon": {
+ "16": "images/iconx16.png",
+ "32": "images/iconx32.png"
+ },
+ "default_popup": "popup.html"
+ },
+ "background": {
+ "scripts": []
+ },
+ "content_scripts": [],
+ "permissions": ["storage"],
+ "commands": {
+ "_execute_browser_action": {
+ "suggested_key": {
+ "default": "Ctrl+D",
+ "linux": "Ctrl+Shift+V",
+ "mac": "MacCtrl+D"
+ }
+ }
+ }
+}
diff --git a/browser/package-lock.json b/browser/package-lock.json
new file mode 100644
index 00000000..50465029
--- /dev/null
+++ b/browser/package-lock.json
@@ -0,0 +1,9819 @@
+{
+ "name": "dnote-extension",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/helper-module-imports": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
+ "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz",
+ "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "@babel/types": {
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
+ "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
+ "requires": {
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.13",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "@emotion/cache": {
+ "version": "10.0.19",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.19.tgz",
+ "integrity": "sha512-BoiLlk4vEsGBg2dAqGSJu0vJl/PgVtCYLBFJaEO8RmQzPugXewQCXZJNXTDFaRlfCs0W+quesayav4fvaif5WQ==",
+ "requires": {
+ "@emotion/sheet": "0.9.3",
+ "@emotion/stylis": "0.8.4",
+ "@emotion/utils": "0.11.2",
+ "@emotion/weak-memoize": "0.2.4"
+ }
+ },
+ "@emotion/core": {
+ "version": "10.0.17",
+ "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.17.tgz",
+ "integrity": "sha512-gykyjjr0sxzVuZBVTVK4dUmYsorc2qLhdYgSiOVK+m7WXgcYTKZevGWZ7TLAgTZvMelCTvhNq8xnf8FR1IdTbg==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "@emotion/cache": "^10.0.17",
+ "@emotion/css": "^10.0.14",
+ "@emotion/serialize": "^0.11.10",
+ "@emotion/sheet": "0.9.3",
+ "@emotion/utils": "0.11.2"
+ }
+ },
+ "@emotion/css": {
+ "version": "10.0.14",
+ "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.14.tgz",
+ "integrity": "sha512-MozgPkBEWvorcdpqHZE5x1D/PLEHUitALQCQYt2wayf4UNhpgQs2tN0UwHYS4FMy5ROBH+0ALyCFVYJ/ywmwlg==",
+ "requires": {
+ "@emotion/serialize": "^0.11.8",
+ "@emotion/utils": "0.11.2",
+ "babel-plugin-emotion": "^10.0.14"
+ }
+ },
+ "@emotion/hash": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz",
+ "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw=="
+ },
+ "@emotion/memoize": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.3.tgz",
+ "integrity": "sha512-2Md9mH6mvo+ygq1trTeVp2uzAKwE2P7In0cRpD/M9Q70aH8L+rxMLbb3JCN2JoSWsV2O+DdFjfbbXoMoLBczow=="
+ },
+ "@emotion/serialize": {
+ "version": "0.11.11",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.11.tgz",
+ "integrity": "sha512-YG8wdCqoWtuoMxhHZCTA+egL0RSGdHEc+YCsmiSBPBEDNuVeMWtjEWtGrhUterSChxzwnWBXvzSxIFQI/3sHLw==",
+ "requires": {
+ "@emotion/hash": "0.7.3",
+ "@emotion/memoize": "0.7.3",
+ "@emotion/unitless": "0.7.4",
+ "@emotion/utils": "0.11.2",
+ "csstype": "^2.5.7"
+ }
+ },
+ "@emotion/sheet": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.3.tgz",
+ "integrity": "sha512-c3Q6V7Df7jfwSq5AzQWbXHa5soeE4F5cbqi40xn0CzXxWW9/6Mxq48WJEtqfWzbZtW9odZdnRAkwCQwN12ob4A=="
+ },
+ "@emotion/stylis": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.4.tgz",
+ "integrity": "sha512-TLmkCVm8f8gH0oLv+HWKiu7e8xmBIaokhxcEKPh1m8pXiV/akCiq50FvYgOwY42rjejck8nsdQxZlXZ7pmyBUQ=="
+ },
+ "@emotion/unitless": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.4.tgz",
+ "integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ=="
+ },
+ "@emotion/utils": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.2.tgz",
+ "integrity": "sha512-UHX2XklLl3sIaP6oiMmlVzT0J+2ATTVpf0dHQVyPJHTkOITvXfaSqnRk6mdDhV9pR8T/tHc3cex78IKXssmzrA=="
+ },
+ "@emotion/weak-memoize": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.4.tgz",
+ "integrity": "sha512-6PYY5DVdAY1ifaQW6XYTnOMihmBVT27elqSjEoodchsGjzYlEsTQMcEhSud99kVawatyTZRTiVkJ/c6lwbQ7nA=="
+ },
+ "@nodelib/fs.scandir": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.2.tgz",
+ "integrity": "sha512-wrIBsjA5pl13f0RN4Zx4FNWmU71lv03meGKnqRUoCyan17s4V3WL92f3w3AIuWbNnpcrQyFBU5qMavJoB8d27w==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "2.0.2",
+ "run-parallel": "^1.1.9"
+ },
+ "dependencies": {
+ "@nodelib/fs.stat": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz",
+ "integrity": "sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==",
+ "dev": true
+ }
+ }
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.3.tgz",
+ "integrity": "sha512-l6t8xEhfK9Sa4YO5mIRdau7XSOADfmh3jCr0evNHdY+HNkW6xuQhgMH7D73VV6WpZOagrW0UludvMTiifiwTfA==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.2",
+ "fastq": "^1.6.0"
+ }
+ },
+ "@sindresorhus/is": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
+ "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==",
+ "dev": true,
+ "optional": true
+ },
+ "@types/eslint-visitor-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
+ "dev": true
+ },
+ "@types/events": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
+ "dev": true
+ },
+ "@types/glob": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+ "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+ "dev": true,
+ "requires": {
+ "@types/events": "*",
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "@types/json-schema": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
+ "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
+ "dev": true
+ },
+ "@types/minimatch": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "12.7.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz",
+ "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==",
+ "dev": true
+ },
+ "@types/prop-types": {
+ "version": "15.7.3",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
+ "dev": true
+ },
+ "@types/q": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz",
+ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
+ "dev": true,
+ "optional": true
+ },
+ "@types/react": {
+ "version": "16.9.3",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.3.tgz",
+ "integrity": "sha512-Ogb2nSn+2qQv5opoCv7Ls5yFxtyrdUYxp5G+SWTrlGk7dmFKw331GiezCgEZj9U7QeXJi1CDtws9pdXU1zUL4g==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^2.2.0"
+ }
+ },
+ "@types/react-dom": {
+ "version": "16.9.1",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.1.tgz",
+ "integrity": "sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@typescript-eslint/eslint-plugin": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.3.1.tgz",
+ "integrity": "sha512-VqVNEsvemviajlaWm03kVMabc6S3xCHGYuY0fReTrIIOZg+3WzB+wfw6fD3KYKerw5lYxmzogmHOZ0i7YKnuwA==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/experimental-utils": "2.3.1",
+ "eslint-utils": "^1.4.2",
+ "functional-red-black-tree": "^1.0.1",
+ "regexpp": "^2.0.1",
+ "tsutils": "^3.17.1"
+ }
+ },
+ "@typescript-eslint/experimental-utils": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.3.1.tgz",
+ "integrity": "sha512-FaZEj73o4h6Wd0Lg+R4pZiJGdR0ZYbJr+O2+RbQ1aZjX8bZcfkVDtD+qm74Dv77rfSKkDKE64UTziLBo9UYHQA==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.3",
+ "@typescript-eslint/typescript-estree": "2.3.1",
+ "eslint-scope": "^5.0.0"
+ }
+ },
+ "@typescript-eslint/parser": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.3.1.tgz",
+ "integrity": "sha512-ZlWdzhCJ2iZnSp/VBAJ/sowFbyHycIux8t0UEH0JsKgQvfSf7949hLYFMwTXdCMeEnpP1zRTHimrR+YHzs8LIw==",
+ "dev": true,
+ "requires": {
+ "@types/eslint-visitor-keys": "^1.0.0",
+ "@typescript-eslint/experimental-utils": "2.3.1",
+ "@typescript-eslint/typescript-estree": "2.3.1",
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+ "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+ "dev": true
+ }
+ }
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.3.1.tgz",
+ "integrity": "sha512-9SFhUgFuePJBB6jlLkOPPhMkZNiDCr+S8Ft7yAkkP2c5x5bxPhG3pe/exMiQaF8IGyVMDW6Ul0q4/cZ+uF3uog==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.4",
+ "is-glob": "^4.0.1",
+ "lodash.unescape": "4.0.1",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "@webassemblyjs/ast": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
+ "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/helper-module-context": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/wast-parser": "1.8.5"
+ }
+ },
+ "@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz",
+ "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-api-error": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz",
+ "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-buffer": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz",
+ "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-code-frame": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz",
+ "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/wast-printer": "1.8.5"
+ }
+ },
+ "@webassemblyjs/helper-fsm": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz",
+ "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-module-context": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz",
+ "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "mamacro": "^0.0.3"
+ }
+ },
+ "@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz",
+ "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-wasm-section": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz",
+ "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-buffer": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/wasm-gen": "1.8.5"
+ }
+ },
+ "@webassemblyjs/ieee754": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz",
+ "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==",
+ "dev": true,
+ "requires": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "@webassemblyjs/leb128": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz",
+ "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==",
+ "dev": true,
+ "requires": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/utf8": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz",
+ "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==",
+ "dev": true
+ },
+ "@webassemblyjs/wasm-edit": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz",
+ "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-buffer": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/helper-wasm-section": "1.8.5",
+ "@webassemblyjs/wasm-gen": "1.8.5",
+ "@webassemblyjs/wasm-opt": "1.8.5",
+ "@webassemblyjs/wasm-parser": "1.8.5",
+ "@webassemblyjs/wast-printer": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wasm-gen": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz",
+ "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/ieee754": "1.8.5",
+ "@webassemblyjs/leb128": "1.8.5",
+ "@webassemblyjs/utf8": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wasm-opt": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz",
+ "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-buffer": "1.8.5",
+ "@webassemblyjs/wasm-gen": "1.8.5",
+ "@webassemblyjs/wasm-parser": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wasm-parser": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz",
+ "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-api-error": "1.8.5",
+ "@webassemblyjs/helper-wasm-bytecode": "1.8.5",
+ "@webassemblyjs/ieee754": "1.8.5",
+ "@webassemblyjs/leb128": "1.8.5",
+ "@webassemblyjs/utf8": "1.8.5"
+ }
+ },
+ "@webassemblyjs/wast-parser": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz",
+ "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/floating-point-hex-parser": "1.8.5",
+ "@webassemblyjs/helper-api-error": "1.8.5",
+ "@webassemblyjs/helper-code-frame": "1.8.5",
+ "@webassemblyjs/helper-fsm": "1.8.5",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/wast-printer": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz",
+ "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/wast-parser": "1.8.5",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
+ },
+ "@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
+ },
+ "aggregate-error": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.0.tgz",
+ "integrity": "sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA==",
+ "dev": true,
+ "requires": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^3.2.0"
+ },
+ "dependencies": {
+ "indent-string": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
+ "dev": true
+ }
+ }
+ },
+ "ajv": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+ "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-errors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+ "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+ "dev": true
+ },
+ "ajv-keywords": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
+ "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
+ "dev": true
+ },
+ "ansi-colors": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
+ "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "^0.1.0"
+ }
+ },
+ "ansi-gray": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
+ "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true,
+ "optional": true
+ },
+ "ansi-wrap": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
+ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
+ "dev": true
+ },
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "dev": true,
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ }
+ },
+ "append-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz",
+ "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=",
+ "dev": true,
+ "requires": {
+ "buffer-equal": "^1.0.0"
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
+ "arch": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz",
+ "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==",
+ "dev": true,
+ "optional": true
+ },
+ "archive-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz",
+ "integrity": "sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "file-type": "^4.2.0"
+ },
+ "dependencies": {
+ "file-type": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz",
+ "integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "archy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
+ "dev": true
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "aria-query": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
+ "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
+ "dev": true,
+ "requires": {
+ "ast-types-flow": "0.0.7",
+ "commander": "^2.11.0"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
+ "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
+ "dev": true
+ }
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-filter": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz",
+ "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=",
+ "dev": true,
+ "requires": {
+ "make-iterator": "^1.0.0"
+ }
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "arr-map": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz",
+ "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=",
+ "dev": true,
+ "requires": {
+ "make-iterator": "^1.0.0"
+ }
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-each": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz",
+ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=",
+ "dev": true
+ },
+ "array-find-index": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+ "dev": true,
+ "optional": true
+ },
+ "array-includes": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
+ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.7.0"
+ }
+ },
+ "array-initial": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz",
+ "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=",
+ "dev": true,
+ "requires": {
+ "array-slice": "^1.0.0",
+ "is-number": "^4.0.0"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+ "dev": true
+ }
+ }
+ },
+ "array-last": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz",
+ "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==",
+ "dev": true,
+ "requires": {
+ "is-number": "^4.0.0"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+ "dev": true
+ }
+ }
+ },
+ "array-slice": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz",
+ "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==",
+ "dev": true
+ },
+ "array-sort": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz",
+ "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==",
+ "dev": true,
+ "requires": {
+ "default-compare": "^1.0.0",
+ "get-value": "^2.0.6",
+ "kind-of": "^5.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "array-uniq": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-2.1.0.tgz",
+ "integrity": "sha512-bdHxtev7FN6+MXI1YFW0Q8mQ8dTJc2S8AMfju+ZR77pbg2yAdVyDlwkaUI7Har0LyOMRFPHrJ9lYdyjZZswdlQ==",
+ "dev": true,
+ "optional": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "asn1.js": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "assert": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+ "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.1.1",
+ "util": "0.10.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+ "dev": true
+ },
+ "util": {
+ "version": "0.10.3",
+ "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.1"
+ }
+ }
+ }
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "ast-types-flow": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
+ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
+ "dev": true
+ },
+ "async-done": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz",
+ "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.2",
+ "process-nextick-args": "^2.0.0",
+ "stream-exhaust": "^1.0.1"
+ }
+ },
+ "async-each": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
+ "dev": true
+ },
+ "async-settle": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz",
+ "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=",
+ "dev": true,
+ "requires": {
+ "async-done": "^1.2.2"
+ }
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "dev": true
+ },
+ "axobject-query": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
+ "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
+ "dev": true,
+ "requires": {
+ "ast-types-flow": "0.0.7"
+ }
+ },
+ "babel-plugin-emotion": {
+ "version": "10.0.19",
+ "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.19.tgz",
+ "integrity": "sha512-1pJb5uKN/gx6bi3gGr588Krj49sxARI9KmxhtMUa+NRJb6lR3OfC51mh3NlWRsOqdjWlT4cSjnZpnFq5K3T5ZA==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@emotion/hash": "0.7.3",
+ "@emotion/memoize": "0.7.3",
+ "@emotion/serialize": "^0.11.11",
+ "babel-plugin-macros": "^2.0.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^1.0.5",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7"
+ }
+ },
+ "babel-plugin-macros": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz",
+ "integrity": "sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ==",
+ "requires": {
+ "@babel/runtime": "^7.4.2",
+ "cosmiconfig": "^5.2.0",
+ "resolve": "^1.10.0"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+ "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ }
+ }
+ },
+ "babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+ },
+ "bach": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz",
+ "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=",
+ "dev": true,
+ "requires": {
+ "arr-filter": "^1.1.1",
+ "arr-flatten": "^1.0.1",
+ "arr-map": "^2.0.0",
+ "array-each": "^1.0.0",
+ "array-initial": "^1.0.0",
+ "array-last": "^1.1.1",
+ "async-done": "^1.2.2",
+ "async-settle": "^1.0.0",
+ "now-and-later": "^2.0.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "dev": true,
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "base64-js": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+ "dev": true
+ },
+ "big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true
+ },
+ "bin-build": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz",
+ "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "decompress": "^4.0.0",
+ "download": "^6.2.2",
+ "execa": "^0.7.0",
+ "p-map-series": "^1.0.0",
+ "tempfile": "^2.0.0"
+ }
+ },
+ "bin-check": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz",
+ "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "execa": "^0.7.0",
+ "executable": "^4.1.0"
+ }
+ },
+ "bin-version": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz",
+ "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "find-versions": "^3.0.0"
+ },
+ "dependencies": {
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ }
+ }
+ },
+ "bin-version-check": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz",
+ "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bin-version": "^3.0.0",
+ "semver": "^5.6.0",
+ "semver-truncate": "^1.1.2"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "bin-wrapper": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz",
+ "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bin-check": "^4.1.0",
+ "bin-version-check": "^4.0.0",
+ "download": "^7.1.0",
+ "import-lazy": "^3.1.0",
+ "os-filter-obj": "^2.0.0",
+ "pify": "^4.0.1"
+ },
+ "dependencies": {
+ "download": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz",
+ "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "archive-type": "^4.0.0",
+ "caw": "^2.0.1",
+ "content-disposition": "^0.5.2",
+ "decompress": "^4.2.0",
+ "ext-name": "^5.0.0",
+ "file-type": "^8.1.0",
+ "filenamify": "^2.0.0",
+ "get-stream": "^3.0.0",
+ "got": "^8.3.1",
+ "make-dir": "^1.2.0",
+ "p-event": "^2.1.0",
+ "pify": "^3.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "file-type": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz",
+ "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==",
+ "dev": true,
+ "optional": true
+ },
+ "got": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz",
+ "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@sindresorhus/is": "^0.7.0",
+ "cacheable-request": "^2.1.1",
+ "decompress-response": "^3.3.0",
+ "duplexer3": "^0.1.4",
+ "get-stream": "^3.0.0",
+ "into-stream": "^3.1.0",
+ "is-retry-allowed": "^1.1.0",
+ "isurl": "^1.0.0-alpha5",
+ "lowercase-keys": "^1.0.0",
+ "mimic-response": "^1.0.0",
+ "p-cancelable": "^0.4.0",
+ "p-timeout": "^2.0.1",
+ "pify": "^3.0.0",
+ "safe-buffer": "^5.1.1",
+ "timed-out": "^4.0.1",
+ "url-parse-lax": "^3.0.0",
+ "url-to-options": "^1.0.1"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "pify": "^3.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "p-cancelable": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
+ "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==",
+ "dev": true,
+ "optional": true
+ },
+ "p-event": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz",
+ "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "p-timeout": "^2.0.1"
+ }
+ },
+ "p-timeout": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
+ "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
+ "dev": true,
+ "requires": {
+ "p-finally": "^1.0.0"
+ }
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true,
+ "optional": true
+ },
+ "prepend-http": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
+ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
+ "dev": true,
+ "optional": true
+ },
+ "url-parse-lax": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
+ "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "prepend-http": "^2.0.0"
+ }
+ }
+ }
+ },
+ "binary-extensions": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+ "dev": true
+ },
+ "binaryextensions": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.2.tgz",
+ "integrity": "sha512-xVNN69YGDghOqCCtA6FI7avYrr02mTJjOgB0/f1VPD3pJC8QEvjTKWc4epDx8AqxxA75NI0QpVM2gPJXUbE4Tg==",
+ "dev": true
+ },
+ "bl": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
+ "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.3.5",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "bluebird": {
+ "version": "3.5.5",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
+ "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "dev": true
+ },
+ "body": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz",
+ "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=",
+ "dev": true,
+ "requires": {
+ "continuable-cache": "^0.3.1",
+ "error": "^7.0.0",
+ "raw-body": "~1.1.0",
+ "safe-json-parse": "~1.0.1"
+ }
+ },
+ "boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "^1.0.4",
+ "browserify-des": "^1.0.0",
+ "evp_bytestokey": "^1.0.0"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "des.js": "^1.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "randombytes": "^2.0.1"
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.1",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.2",
+ "elliptic": "^6.0.0",
+ "inherits": "^2.0.1",
+ "parse-asn1": "^5.0.0"
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
+ "buffer": {
+ "version": "5.4.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
+ "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4"
+ }
+ },
+ "buffer-alloc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+ "dev": true,
+ "requires": {
+ "buffer-alloc-unsafe": "^1.1.0",
+ "buffer-fill": "^1.0.0"
+ }
+ },
+ "buffer-alloc-unsafe": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+ "dev": true
+ },
+ "buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
+ "dev": true
+ },
+ "buffer-equal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
+ "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=",
+ "dev": true
+ },
+ "buffer-fill": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+ "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+ "dev": true
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz",
+ "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=",
+ "dev": true
+ },
+ "cacache": {
+ "version": "12.0.3",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
+ "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.5.5",
+ "chownr": "^1.1.1",
+ "figgy-pudding": "^3.5.1",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.1.15",
+ "infer-owner": "^1.0.3",
+ "lru-cache": "^5.1.1",
+ "mississippi": "^3.0.0",
+ "mkdirp": "^0.5.1",
+ "move-concurrently": "^1.0.1",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^2.6.3",
+ "ssri": "^6.0.1",
+ "unique-filename": "^1.1.1",
+ "y18n": "^4.0.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
+ "dev": true
+ }
+ }
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "dev": true,
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ }
+ },
+ "cacheable-request": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz",
+ "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "clone-response": "1.0.2",
+ "get-stream": "3.0.0",
+ "http-cache-semantics": "3.8.1",
+ "keyv": "3.0.0",
+ "lowercase-keys": "1.0.0",
+ "normalize-url": "2.0.1",
+ "responselike": "1.0.2"
+ },
+ "dependencies": {
+ "lowercase-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+ "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "caller-callsite": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+ "requires": {
+ "callsites": "^2.0.0"
+ }
+ },
+ "caller-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+ "requires": {
+ "caller-callsite": "^2.0.0"
+ }
+ },
+ "callsites": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+ "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
+ },
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+ "dev": true,
+ "optional": true
+ },
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "camelcase": "^2.0.0",
+ "map-obj": "^1.0.0"
+ }
+ },
+ "caw": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz",
+ "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==",
+ "dev": true,
+ "requires": {
+ "get-proxy": "^2.0.0",
+ "isurl": "^1.0.0-alpha5",
+ "tunnel-agent": "^0.6.0",
+ "url-to-options": "^1.0.1"
+ }
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ }
+ }
+ },
+ "chownr": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
+ "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
+ "dev": true
+ },
+ "chrome-trace-event": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+ "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "classnames": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ },
+ "clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true
+ },
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+ "dev": true
+ },
+ "clone-buffer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz",
+ "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=",
+ "dev": true
+ },
+ "clone-response": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
+ "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "mimic-response": "^1.0.0"
+ }
+ },
+ "clone-stats": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz",
+ "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=",
+ "dev": true
+ },
+ "cloneable-readable": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz",
+ "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "process-nextick-args": "^2.0.0",
+ "readable-stream": "^2.3.5"
+ }
+ },
+ "coa": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz",
+ "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@types/q": "^1.5.1",
+ "chalk": "^2.4.1",
+ "q": "^1.1.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "collection-map": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz",
+ "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=",
+ "dev": true,
+ "requires": {
+ "arr-map": "^2.0.2",
+ "for-own": "^1.0.0",
+ "make-iterator": "^1.0.0"
+ }
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
+ "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
+ "dev": true,
+ "requires": {
+ "graceful-readlink": ">= 1.0.0"
+ }
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "concurrently": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.2.tgz",
+ "integrity": "sha512-Kim9SFrNr2jd8/0yNYqDTFALzUX1tvimmwFWxmp/D4mRI+kbqIIwE2RkBDrxS2ic25O1UgQMI5AtBqdtX3ynYg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "date-fns": "^1.30.1",
+ "lodash": "^4.17.15",
+ "read-pkg": "^4.0.1",
+ "rxjs": "^6.5.2",
+ "spawn-command": "^0.0.2-1",
+ "supports-color": "^4.5.0",
+ "tree-kill": "^1.2.1",
+ "yargs": "^12.0.5"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "invert-kv": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "lcid": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^2.0.0"
+ }
+ },
+ "os-locale": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+ "dev": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "lcid": "^2.0.0",
+ "mem": "^4.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ },
+ "read-pkg": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
+ "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
+ "dev": true,
+ "requires": {
+ "normalize-package-data": "^2.3.2",
+ "parse-json": "^4.0.0",
+ "pify": "^3.0.0"
+ }
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
+ "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
+ "dev": true,
+ "requires": {
+ "has-flag": "^2.0.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+ "dev": true
+ }
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "12.0.5",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^3.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^11.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "config-chain": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
+ "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
+ "dev": true,
+ "requires": {
+ "ini": "^1.3.4",
+ "proto-list": "~1.2.1"
+ }
+ },
+ "confusing-browser-globals": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
+ "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==",
+ "dev": true
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+ "dev": true,
+ "requires": {
+ "date-now": "^0.1.4"
+ }
+ },
+ "console-stream": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/console-stream/-/console-stream-0.1.1.tgz",
+ "integrity": "sha1-oJX+B7IEZZVfL6/Si11yvM2UnUQ=",
+ "dev": true,
+ "optional": true
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "contains-path": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+ "dev": true
+ },
+ "content-disposition": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "continuable-cache": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz",
+ "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
+ "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "copy-concurrently": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "fs-write-stream-atomic": "^1.0.8",
+ "iferr": "^0.1.5",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.0"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
+ },
+ "copy-props": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz",
+ "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==",
+ "dev": true,
+ "requires": {
+ "each-props": "^1.3.0",
+ "is-plain-object": "^2.0.1"
+ }
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "cosmiconfig": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+ "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+ "requires": {
+ "import-fresh": "^2.0.0",
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.13.1",
+ "parse-json": "^4.0.0"
+ },
+ "dependencies": {
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ }
+ }
+ },
+ "create-ecdh": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.0.0"
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
+ "dev": true
+ }
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "^1.0.0",
+ "browserify-sign": "^4.0.0",
+ "create-ecdh": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.0",
+ "diffie-hellman": "^5.0.0",
+ "inherits": "^2.0.1",
+ "pbkdf2": "^3.0.3",
+ "public-encrypt": "^4.0.0",
+ "randombytes": "^2.0.0",
+ "randomfill": "^1.0.3"
+ }
+ },
+ "css-select": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz",
+ "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "boolbase": "^1.0.0",
+ "css-what": "^2.1.2",
+ "domutils": "^1.7.0",
+ "nth-check": "^1.0.2"
+ }
+ },
+ "css-select-base-adapter": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
+ "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==",
+ "dev": true,
+ "optional": true
+ },
+ "css-tree": {
+ "version": "1.0.0-alpha.33",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.33.tgz",
+ "integrity": "sha512-SPt57bh5nQnpsTBsx/IXbO14sRc9xXu5MtMAVuo0BaQQmyf0NupNPPSoMaqiAF5tDFafYsTkfeH4Q/HCKXkg4w==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "mdn-data": "2.0.4",
+ "source-map": "^0.5.3"
+ }
+ },
+ "css-what": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
+ "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
+ "dev": true,
+ "optional": true
+ },
+ "csso": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz",
+ "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "css-tree": "1.0.0-alpha.29"
+ },
+ "dependencies": {
+ "css-tree": {
+ "version": "1.0.0-alpha.29",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz",
+ "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "mdn-data": "~1.1.0",
+ "source-map": "^0.5.3"
+ }
+ },
+ "mdn-data": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz",
+ "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "csstype": {
+ "version": "2.6.6",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz",
+ "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg=="
+ },
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "array-find-index": "^1.0.1"
+ }
+ },
+ "cyclist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+ "dev": true
+ },
+ "d": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+ "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+ "dev": true,
+ "requires": {
+ "es5-ext": "^0.10.50",
+ "type": "^1.0.1"
+ }
+ },
+ "damerau-levenshtein": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz",
+ "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==",
+ "dev": true
+ },
+ "date-fns": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
+ "dev": true
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
+ "decompress": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz",
+ "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=",
+ "dev": true,
+ "requires": {
+ "decompress-tar": "^4.0.0",
+ "decompress-tarbz2": "^4.0.0",
+ "decompress-targz": "^4.0.0",
+ "decompress-unzip": "^4.0.1",
+ "graceful-fs": "^4.1.10",
+ "make-dir": "^1.0.0",
+ "pify": "^2.3.0",
+ "strip-dirs": "^2.0.0"
+ },
+ "dependencies": {
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "dev": true,
+ "requires": {
+ "pify": "^3.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ }
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "decompress-response": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
+ "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
+ "dev": true,
+ "requires": {
+ "mimic-response": "^1.0.0"
+ }
+ },
+ "decompress-tar": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz",
+ "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==",
+ "dev": true,
+ "requires": {
+ "file-type": "^5.2.0",
+ "is-stream": "^1.1.0",
+ "tar-stream": "^1.5.2"
+ },
+ "dependencies": {
+ "file-type": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
+ "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
+ "dev": true
+ }
+ }
+ },
+ "decompress-tarbz2": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz",
+ "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==",
+ "dev": true,
+ "requires": {
+ "decompress-tar": "^4.1.0",
+ "file-type": "^6.1.0",
+ "is-stream": "^1.1.0",
+ "seek-bzip": "^1.0.5",
+ "unbzip2-stream": "^1.0.9"
+ },
+ "dependencies": {
+ "file-type": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz",
+ "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==",
+ "dev": true
+ }
+ }
+ },
+ "decompress-targz": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz",
+ "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==",
+ "dev": true,
+ "requires": {
+ "decompress-tar": "^4.1.1",
+ "file-type": "^5.2.0",
+ "is-stream": "^1.1.0"
+ },
+ "dependencies": {
+ "file-type": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
+ "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
+ "dev": true
+ }
+ }
+ },
+ "decompress-unzip": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz",
+ "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=",
+ "dev": true,
+ "requires": {
+ "file-type": "^3.8.0",
+ "get-stream": "^2.2.0",
+ "pify": "^2.3.0",
+ "yauzl": "^2.4.2"
+ },
+ "dependencies": {
+ "file-type": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
+ "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
+ "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.0.1",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "deep-diff": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
+ "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ="
+ },
+ "default-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz",
+ "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^5.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "default-resolution": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz",
+ "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=",
+ "dev": true
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "del": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz",
+ "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==",
+ "dev": true,
+ "requires": {
+ "globby": "^10.0.1",
+ "graceful-fs": "^4.2.2",
+ "is-glob": "^4.0.1",
+ "is-path-cwd": "^2.2.0",
+ "is-path-inside": "^3.0.1",
+ "p-map": "^3.0.0",
+ "rimraf": "^3.0.0",
+ "slash": "^3.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "rimraf": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz",
+ "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true
+ }
+ }
+ },
+ "des.js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
+ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "detect-file": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ }
+ },
+ "dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
+ "dom-serializer": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
+ "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "entities": "^2.0.0"
+ },
+ "dependencies": {
+ "domelementtype": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
+ "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
+ "dev": true,
+ "optional": true
+ },
+ "domutils": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+ "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "download": {
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz",
+ "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "caw": "^2.0.0",
+ "content-disposition": "^0.5.2",
+ "decompress": "^4.0.0",
+ "ext-name": "^5.0.0",
+ "file-type": "5.2.0",
+ "filenamify": "^2.0.0",
+ "get-stream": "^3.0.0",
+ "got": "^7.0.0",
+ "make-dir": "^1.0.0",
+ "p-event": "^1.0.0",
+ "pify": "^3.0.0"
+ },
+ "dependencies": {
+ "file-type": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
+ "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
+ "dev": true,
+ "optional": true
+ },
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ }
+ }
+ },
+ "duplexer3": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
+ "dev": true
+ },
+ "duplexify": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz",
+ "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ },
+ "dependencies": {
+ "end-of-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ }
+ }
+ },
+ "each-props": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz",
+ "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.1",
+ "object.defaults": "^1.1.0"
+ }
+ },
+ "editions": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz",
+ "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
+ "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.4.0",
+ "brorand": "^1.0.1",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.0"
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "end-of-stream": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.2.tgz",
+ "integrity": "sha512-gUSUszrsxlDnUbUwEI9Oygyrk4ZEWtVaHQc+uZHphVeNxl+qeqMV/jDWoTkjN1RmGlZ5QWAP7o458p/JMlikQg==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "enhanced-resolve": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
+ "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.4.0",
+ "tapable": "^1.0.0"
+ }
+ },
+ "entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
+ "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
+ "dev": true,
+ "optional": true
+ },
+ "errno": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+ "dev": true,
+ "requires": {
+ "prr": "~1.0.1"
+ }
+ },
+ "error": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/error/-/error-7.2.0.tgz",
+ "integrity": "sha512-M6t3j3Vt3uDicrViMP5fLq2AeADNrCVFD8Oj4Qt2MHsX0mPYG7D5XdnEfSdRpaHQzjAJ19wu+I1mw9rQYMTAPg==",
+ "dev": true,
+ "requires": {
+ "string-template": "~0.2.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.14.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz",
+ "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.0",
+ "is-callable": "^1.1.4",
+ "is-regex": "^1.0.4",
+ "object-inspect": "^1.6.0",
+ "object-keys": "^1.1.1",
+ "string.prototype.trimleft": "^2.0.0",
+ "string.prototype.trimright": "^2.0.0"
+ },
+ "dependencies": {
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
+ }
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "es5-ext": {
+ "version": "0.10.51",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz",
+ "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==",
+ "dev": true,
+ "requires": {
+ "es6-iterator": "~2.0.3",
+ "es6-symbol": "~3.1.1",
+ "next-tick": "^1.0.0"
+ }
+ },
+ "es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
+ "dev": true,
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
+ "es6-symbol": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz",
+ "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==",
+ "dev": true,
+ "requires": {
+ "d": "^1.0.1",
+ "es5-ext": "^0.10.51"
+ }
+ },
+ "es6-weak-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
+ "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
+ "dev": true,
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.46",
+ "es6-iterator": "^2.0.3",
+ "es6-symbol": "^3.1.1"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+ },
+ "eslint-config-airbnb": {
+ "version": "18.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.0.1.tgz",
+ "integrity": "sha512-hLb/ccvW4grVhvd6CT83bECacc+s4Z3/AEyWQdIT2KeTsG9dR7nx1gs7Iw4tDmGKozCNHFn4yZmRm3Tgy+XxyQ==",
+ "dev": true,
+ "requires": {
+ "eslint-config-airbnb-base": "^14.0.0",
+ "object.assign": "^4.1.0",
+ "object.entries": "^1.1.0"
+ }
+ },
+ "eslint-config-airbnb-base": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz",
+ "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.7",
+ "object.assign": "^4.1.0",
+ "object.entries": "^1.1.0"
+ }
+ },
+ "eslint-import-resolver-node": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
+ "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.9",
+ "resolve": "^1.5.0"
+ }
+ },
+ "eslint-module-utils": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz",
+ "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.8",
+ "pkg-dir": "^2.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ }
+ }
+ },
+ "eslint-plugin-import": {
+ "version": "2.18.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz",
+ "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3",
+ "contains-path": "^0.1.0",
+ "debug": "^2.6.9",
+ "doctrine": "1.5.0",
+ "eslint-import-resolver-node": "^0.3.2",
+ "eslint-module-utils": "^2.4.0",
+ "has": "^1.0.3",
+ "minimatch": "^3.0.4",
+ "object.values": "^1.1.0",
+ "read-pkg-up": "^2.0.0",
+ "resolve": "^1.11.0"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "isarray": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "load-json-file": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "read-pkg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^2.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^2.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^2.0.0"
+ }
+ },
+ "resolve": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+ "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-jsx-a11y": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz",
+ "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.4.5",
+ "aria-query": "^3.0.0",
+ "array-includes": "^3.0.3",
+ "ast-types-flow": "^0.0.7",
+ "axobject-query": "^2.0.2",
+ "damerau-levenshtein": "^1.0.4",
+ "emoji-regex": "^7.0.2",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.2.1"
+ }
+ },
+ "eslint-plugin-react": {
+ "version": "7.14.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz",
+ "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3",
+ "doctrine": "^2.1.0",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.1.0",
+ "object.entries": "^1.1.0",
+ "object.fromentries": "^2.0.0",
+ "object.values": "^1.1.0",
+ "prop-types": "^15.7.2",
+ "resolve": "^1.10.1"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "resolve": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+ "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ }
+ }
+ },
+ "eslint-plugin-react-hooks": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.0.1.tgz",
+ "integrity": "sha512-xir+3KHKo86AasxlCV8AHRtIZPHljqCRRUYgASkbatmt0fad4+5GgC7zkT7o/06hdKM6MIwp8giHVXqBPaarHQ==",
+ "dev": true
+ },
+ "eslint-scope": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
+ "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
+ "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.0.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+ "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==",
+ "dev": true
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+ },
+ "esrecurse": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.1.0"
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "exec-buffer": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz",
+ "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "execa": "^0.7.0",
+ "p-finally": "^1.0.0",
+ "pify": "^3.0.0",
+ "rimraf": "^2.5.4",
+ "tempfile": "^2.0.0"
+ }
+ },
+ "execa": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^5.0.1",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ }
+ }
+ },
+ "executable": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz",
+ "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "pify": "^2.2.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "expand-tilde": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+ "dev": true,
+ "requires": {
+ "homedir-polyfill": "^1.0.1"
+ }
+ },
+ "ext-list": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz",
+ "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==",
+ "dev": true,
+ "requires": {
+ "mime-db": "^1.28.0"
+ }
+ },
+ "ext-name": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz",
+ "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==",
+ "dev": true,
+ "requires": {
+ "ext-list": "^2.0.0",
+ "sort-keys-length": "^1.0.0"
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "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"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "fancy-log": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
+ "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==",
+ "dev": true,
+ "requires": {
+ "ansi-gray": "^0.1.1",
+ "color-support": "^1.1.3",
+ "parse-node-version": "^1.0.0",
+ "time-stamp": "^1.0.0"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+ "dev": true
+ },
+ "fastq": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz",
+ "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==",
+ "dev": true,
+ "requires": {
+ "reusify": "^1.0.0"
+ }
+ },
+ "faye-websocket": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+ "dev": true,
+ "requires": {
+ "websocket-driver": ">=0.5.1"
+ }
+ },
+ "fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
+ "dev": true,
+ "requires": {
+ "pend": "~1.2.0"
+ }
+ },
+ "figgy-pudding": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
+ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
+ "dev": true
+ },
+ "file-type": {
+ "version": "12.3.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.3.0.tgz",
+ "integrity": "sha512-4E4Esq9KLwjYCY32E7qSmd0h7LefcniZHX+XcdJ4Wfx1uGJX7QCigiqw/U0yT7WOslm28yhxl87DJ0wHYv0RAA==",
+ "dev": true
+ },
+ "filename-reserved-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
+ "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=",
+ "dev": true
+ },
+ "filenamify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz",
+ "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==",
+ "dev": true,
+ "requires": {
+ "filename-reserved-regex": "^2.0.0",
+ "strip-outer": "^1.0.0",
+ "trim-repeated": "^1.0.0"
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ },
+ "dependencies": {
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ }
+ }
+ },
+ "find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true,
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "find-versions": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.1.0.tgz",
+ "integrity": "sha512-NCTfNiVzeE/xL+roNDffGuRbrWI6atI18lTJ22vKp7rs2OhYzMK3W1dIdO2TUndH/QMcacM4d1uWwgcZcHK69Q==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "array-uniq": "^2.1.0",
+ "semver-regex": "^2.0.0"
+ }
+ },
+ "findup-sync": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
+ "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
+ "dev": true,
+ "requires": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "micromatch": "^3.0.4",
+ "resolve-dir": "^1.0.1"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ }
+ }
+ },
+ "fined": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz",
+ "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.2",
+ "is-plain-object": "^2.0.3",
+ "object.defaults": "^1.1.0",
+ "object.pick": "^1.2.0",
+ "parse-filepath": "^1.0.1"
+ }
+ },
+ "flagged-respawn": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
+ "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==",
+ "dev": true
+ },
+ "flush-write-stream": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+ "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.3.6"
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "for-own": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
+ "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.1"
+ }
+ },
+ "fork-stream": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz",
+ "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=",
+ "dev": true
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true
+ },
+ "fs-mkdirp-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz",
+ "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "through2": "^2.0.3"
+ }
+ },
+ "fs-write-stream-atomic": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "iferr": "^0.1.5",
+ "imurmurhash": "^0.1.4",
+ "readable-stream": "1 || 2"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz",
+ "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "nan": "^2.12.1",
+ "node-pre-gyp": "^0.12.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true,
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "dev": true
+ },
+ "minipass": {
+ "version": "2.3.5",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.2.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.3.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "^4.1.0",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.12.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.1",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.2.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.4.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "bundled": true,
+ "dev": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.7.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.8",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.1.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.3.4",
+ "minizlib": "^1.1.1",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.2",
+ "yallist": "^3.0.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true
+ },
+ "yallist": {
+ "version": "3.0.3",
+ "bundled": true,
+ "dev": true
+ }
+ }
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+ "dev": true
+ },
+ "get-proxy": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz",
+ "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==",
+ "dev": true,
+ "requires": {
+ "npm-conf": "^1.1.0"
+ }
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+ "dev": true
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
+ "gifsicle": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/gifsicle/-/gifsicle-4.0.1.tgz",
+ "integrity": "sha512-A/kiCLfDdV+ERV/UB+2O41mifd+RxH8jlRG8DMxZO84Bma/Fw0htqZ+hY2iaalLRNyUu7tYZQslqUBJxBggxbg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bin-build": "^3.0.0",
+ "bin-wrapper": "^4.0.0",
+ "execa": "^1.0.0",
+ "logalot": "^2.0.0"
+ },
+ "dependencies": {
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ }
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ }
+ },
+ "glob-stream": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz",
+ "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=",
+ "dev": true,
+ "requires": {
+ "extend": "^3.0.0",
+ "glob": "^7.1.1",
+ "glob-parent": "^3.1.0",
+ "is-negated-glob": "^1.0.0",
+ "ordered-read-streams": "^1.0.0",
+ "pumpify": "^1.3.5",
+ "readable-stream": "^2.1.5",
+ "remove-trailing-separator": "^1.0.1",
+ "to-absolute-glob": "^2.0.0",
+ "unique-stream": "^2.0.2"
+ }
+ },
+ "glob-watcher": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz",
+ "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-done": "^1.2.0",
+ "chokidar": "^2.0.0",
+ "is-negated-glob": "^1.0.0",
+ "just-debounce": "^1.0.0",
+ "object.defaults": "^1.1.0"
+ }
+ },
+ "global-modules": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^1.0.1",
+ "is-windows": "^1.0.1",
+ "resolve-dir": "^1.0.0"
+ }
+ },
+ "global-prefix": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+ "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.2",
+ "homedir-polyfill": "^1.0.1",
+ "ini": "^1.3.4",
+ "is-windows": "^1.0.1",
+ "which": "^1.2.14"
+ }
+ },
+ "globby": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz",
+ "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==",
+ "dev": true,
+ "requires": {
+ "@types/glob": "^7.1.1",
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.0.3",
+ "glob": "^7.1.3",
+ "ignore": "^5.1.1",
+ "merge2": "^1.2.3",
+ "slash": "^3.0.0"
+ },
+ "dependencies": {
+ "@nodelib/fs.stat": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.2.tgz",
+ "integrity": "sha512-z8+wGWV2dgUhLqrtRYa03yDx4HWMvXKi1z8g3m2JyxAx8F7xk74asqPk5LAETjqDSGLFML/6CDl0+yFunSYicw==",
+ "dev": true
+ },
+ "array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "requires": {
+ "path-type": "^4.0.0"
+ }
+ },
+ "fast-glob": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.0.4.tgz",
+ "integrity": "sha512-wkIbV6qg37xTJwqSsdnIphL1e+LaGz4AIQqr00mIubMaEhv1/HEmJ0uuCGZRNRUkZZmOB5mJKO0ZUTVq+SxMQg==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "^2.0.1",
+ "@nodelib/fs.walk": "^1.2.1",
+ "glob-parent": "^5.0.0",
+ "is-glob": "^4.0.1",
+ "merge2": "^1.2.3",
+ "micromatch": "^4.0.2"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+ "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.0.5"
+ }
+ },
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true
+ },
+ "slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "glogg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz",
+ "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==",
+ "dev": true,
+ "requires": {
+ "sparkles": "^1.0.0"
+ }
+ },
+ "got": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz",
+ "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "decompress-response": "^3.2.0",
+ "duplexer3": "^0.1.4",
+ "get-stream": "^3.0.0",
+ "is-plain-obj": "^1.1.0",
+ "is-retry-allowed": "^1.0.0",
+ "is-stream": "^1.0.0",
+ "isurl": "^1.0.0-alpha5",
+ "lowercase-keys": "^1.0.0",
+ "p-cancelable": "^0.3.0",
+ "p-timeout": "^1.1.1",
+ "safe-buffer": "^5.0.1",
+ "timed-out": "^4.0.0",
+ "url-parse-lax": "^1.0.0",
+ "url-to-options": "^1.0.1"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
+ "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
+ "dev": true
+ },
+ "graceful-readlink": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
+ "dev": true
+ },
+ "gulp": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
+ "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==",
+ "dev": true,
+ "requires": {
+ "glob-watcher": "^5.0.3",
+ "gulp-cli": "^2.2.0",
+ "undertaker": "^1.2.1",
+ "vinyl-fs": "^3.0.0"
+ },
+ "dependencies": {
+ "gulp-cli": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz",
+ "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^1.0.1",
+ "archy": "^1.0.0",
+ "array-sort": "^1.0.0",
+ "color-support": "^1.1.3",
+ "concat-stream": "^1.6.0",
+ "copy-props": "^2.0.1",
+ "fancy-log": "^1.3.2",
+ "gulplog": "^1.0.0",
+ "interpret": "^1.1.0",
+ "isobject": "^3.0.1",
+ "liftoff": "^3.1.0",
+ "matchdep": "^2.0.0",
+ "mute-stdout": "^1.0.0",
+ "pretty-hrtime": "^1.0.0",
+ "replace-homedir": "^1.0.0",
+ "semver-greatest-satisfied-range": "^1.1.0",
+ "v8flags": "^3.0.1",
+ "yargs": "^7.1.0"
+ }
+ }
+ }
+ },
+ "gulp-if": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz",
+ "integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==",
+ "dev": true,
+ "requires": {
+ "gulp-match": "^1.1.0",
+ "ternary-stream": "^3.0.0",
+ "through2": "^3.0.1"
+ },
+ "dependencies": {
+ "through2": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
+ "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "2 || 3"
+ }
+ }
+ }
+ },
+ "gulp-imagemin": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/gulp-imagemin/-/gulp-imagemin-6.1.0.tgz",
+ "integrity": "sha512-0TPkak5BsiRfw+kfcKwIcODbOHHcTyvBM9arlRSwXdUVzrGAcq/7urZoOQD5n4uWvKhjg+l9/yn1GDZsDuWUow==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "fancy-log": "^1.3.2",
+ "imagemin": "^7.0.0",
+ "imagemin-gifsicle": "^6.0.1",
+ "imagemin-jpegtran": "^6.0.0",
+ "imagemin-optipng": "^7.0.0",
+ "imagemin-svgo": "^7.0.0",
+ "plugin-error": "^1.0.1",
+ "plur": "^3.0.1",
+ "pretty-bytes": "^5.3.0",
+ "through2-concurrent": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "pretty-bytes": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz",
+ "integrity": "sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "gulp-livereload": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/gulp-livereload/-/gulp-livereload-4.0.2.tgz",
+ "integrity": "sha512-InmaR50Xl1xB1WdEk4mrUgGHv3VhhlRLrx7u60iY5AAer90FlK95KXitPcGGQoi28zrUJM189d/h6+V470Ncgg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "debug": "^3.1.0",
+ "fancy-log": "^1.3.2",
+ "lodash.assign": "^4.2.0",
+ "readable-stream": "^3.0.6",
+ "tiny-lr": "^1.1.1",
+ "vinyl": "^2.2.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+ "dev": true
+ },
+ "clone-stats": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz",
+ "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=",
+ "dev": true
+ },
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
+ "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "replace-ext": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
+ "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "vinyl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz",
+ "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==",
+ "dev": true,
+ "requires": {
+ "clone": "^2.1.1",
+ "clone-buffer": "^1.0.0",
+ "clone-stats": "^1.0.0",
+ "cloneable-readable": "^1.0.0",
+ "remove-trailing-separator": "^1.0.1",
+ "replace-ext": "^1.0.0"
+ }
+ }
+ }
+ },
+ "gulp-match": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz",
+ "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==",
+ "dev": true,
+ "requires": {
+ "minimatch": "^3.0.3"
+ }
+ },
+ "gulp-replace": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz",
+ "integrity": "sha512-lgdmrFSI1SdhNMXZQbrC75MOl1UjYWlOWNbNRnz+F/KHmgxt3l6XstBoAYIdadwETFyG/6i+vWUSCawdC3pqOw==",
+ "dev": true,
+ "requires": {
+ "istextorbinary": "2.2.1",
+ "readable-stream": "^2.0.1",
+ "replacestream": "^4.0.0"
+ }
+ },
+ "gulp-zip": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-5.0.0.tgz",
+ "integrity": "sha512-oR3t8kn+ccHkSyRcBV5kBLPXrhqTh5d6wBAR7r7wqjNQNBhYvOwPedCwlAaGcNl1qSeXNDn6qOk1Qyxvx9Wrow==",
+ "dev": true,
+ "requires": {
+ "get-stream": "^5.1.0",
+ "plugin-error": "^1.0.1",
+ "through2": "^3.0.1",
+ "vinyl": "^2.1.0",
+ "yazl": "^2.5.1"
+ },
+ "dependencies": {
+ "get-stream": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
+ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "through2": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
+ "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "2 || 3"
+ }
+ }
+ }
+ },
+ "gulplog": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz",
+ "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=",
+ "dev": true,
+ "requires": {
+ "glogg": "^1.0.0"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-symbol-support-x": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz",
+ "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+ "dev": true
+ },
+ "has-to-string-tag-x": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz",
+ "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==",
+ "dev": true,
+ "requires": {
+ "has-symbol-support-x": "^1.4.1"
+ }
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "hoist-non-react-statics": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
+ "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "homedir-polyfill": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz",
+ "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=",
+ "dev": true,
+ "requires": {
+ "parse-passwd": "^1.0.0"
+ }
+ },
+ "hosted-git-info": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
+ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
+ "dev": true
+ },
+ "html-comment-regex": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz",
+ "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==",
+ "dev": true,
+ "optional": true
+ },
+ "http-cache-semantics": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
+ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==",
+ "dev": true,
+ "optional": true
+ },
+ "http-parser-js": {
+ "version": "0.4.10",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
+ "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=",
+ "dev": true
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "ieee754": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
+ "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==",
+ "dev": true
+ },
+ "iferr": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+ "dev": true
+ },
+ "ignore": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
+ "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
+ "dev": true
+ },
+ "imagemin": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.0.tgz",
+ "integrity": "sha512-TXvCSSIYl4KQUASur9S0+E4olVECzvxvZABU9rNqsza7vzIrUQMRTjyczGf8OmtcgvZ9jOYyinXW3epOpd/04A==",
+ "dev": true,
+ "requires": {
+ "file-type": "^12.0.0",
+ "globby": "^10.0.0",
+ "junk": "^3.1.0",
+ "make-dir": "^3.0.0",
+ "p-pipe": "^3.0.0",
+ "replace-ext": "^1.0.0"
+ }
+ },
+ "imagemin-gifsicle": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/imagemin-gifsicle/-/imagemin-gifsicle-6.0.1.tgz",
+ "integrity": "sha512-kuu47c6iKDQ6R9J10xCwL0lgs0+sMz3LRHqRcJ2CRBWdcNmo3T5hUaM8hSZfksptZXJLGKk8heSAvwtSdB1Fng==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "exec-buffer": "^3.0.0",
+ "gifsicle": "^4.0.0",
+ "is-gif": "^3.0.0"
+ }
+ },
+ "imagemin-jpegtran": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/imagemin-jpegtran/-/imagemin-jpegtran-6.0.0.tgz",
+ "integrity": "sha512-Ih+NgThzqYfEWv9t58EItncaaXIHR0u9RuhKa8CtVBlMBvY0dCIxgQJQCfwImA4AV1PMfmUKlkyIHJjb7V4z1g==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "exec-buffer": "^3.0.0",
+ "is-jpg": "^2.0.0",
+ "jpegtran-bin": "^4.0.0"
+ }
+ },
+ "imagemin-optipng": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-7.0.0.tgz",
+ "integrity": "sha512-N40bmLgiyv5H8xFp/RYmWKdg6Z19MGqzcNW+IWXG7VPrLV75NbcOn8y6A7eZcSHOCNW+DqBx+b95yw+Tf6Sl/g==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "exec-buffer": "^3.0.0",
+ "is-png": "^2.0.0",
+ "optipng-bin": "^6.0.0"
+ }
+ },
+ "imagemin-svgo": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/imagemin-svgo/-/imagemin-svgo-7.0.0.tgz",
+ "integrity": "sha512-+iGJFaPIMx8TjFW6zN+EkOhlqcemdL7F3N3Y0wODvV2kCUBuUtZK7DRZc1+Zfu4U2W/lTMUyx2G8YMOrZntIWg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-svg": "^3.0.0",
+ "svgo": "^1.0.5"
+ }
+ },
+ "import-fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+ "requires": {
+ "caller-path": "^2.0.0",
+ "resolve-from": "^3.0.0"
+ }
+ },
+ "import-lazy": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz",
+ "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==",
+ "dev": true,
+ "optional": true
+ },
+ "import-local": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+ "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+ "dev": true,
+ "requires": {
+ "pkg-dir": "^3.0.0",
+ "resolve-cwd": "^2.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+ "dev": true,
+ "requires": {
+ "repeating": "^2.0.0"
+ }
+ },
+ "infer-owner": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+ "dev": true
+ },
+ "interpret": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
+ "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
+ "dev": true
+ },
+ "into-stream": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
+ "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "from2": "^2.1.1",
+ "p-is-promise": "^1.1.0"
+ }
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "invert-kv": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
+ "dev": true
+ },
+ "irregular-plurals": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz",
+ "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==",
+ "dev": true
+ },
+ "is-absolute": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
+ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
+ "dev": true,
+ "requires": {
+ "is-relative": "^1.0.0",
+ "is-windows": "^1.0.1"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "dev": true,
+ "requires": {
+ "builtin-modules": "^1.0.0"
+ }
+ },
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+ "dev": true
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+ "dev": true
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "is-directory": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-finite": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-gif": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz",
+ "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "file-type": "^10.4.0"
+ },
+ "dependencies": {
+ "file-type": {
+ "version": "10.11.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz",
+ "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ },
+ "is-jpg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-2.0.0.tgz",
+ "integrity": "sha1-LhmX+m6RZuqsAkLarkQ0A+TvHZc=",
+ "dev": true,
+ "optional": true
+ },
+ "is-natural-number": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
+ "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=",
+ "dev": true
+ },
+ "is-negated-glob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
+ "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=",
+ "dev": true
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz",
+ "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=",
+ "dev": true
+ },
+ "is-path-cwd": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
+ "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==",
+ "dev": true
+ },
+ "is-path-inside": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.1.tgz",
+ "integrity": "sha512-CKstxrctq1kUesU6WhtZDbYKzzYBuRH0UYInAVrkc/EYdB9ltbfE0gOoayG9nhohG6447sOOVGhHqsdmBvkbNg==",
+ "dev": true
+ },
+ "is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+ "dev": true
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-png": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz",
+ "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==",
+ "dev": true,
+ "optional": true
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.1"
+ }
+ },
+ "is-relative": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
+ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
+ "dev": true,
+ "requires": {
+ "is-unc-path": "^1.0.0"
+ }
+ },
+ "is-retry-allowed": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+ "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
+ "dev": true
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
+ "is-svg": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz",
+ "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "html-comment-regex": "^1.1.0"
+ }
+ },
+ "is-symbol": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.0"
+ }
+ },
+ "is-unc-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
+ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
+ "dev": true,
+ "requires": {
+ "unc-path-regex": "^0.1.2"
+ }
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+ "dev": true
+ },
+ "is-valid-glob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
+ "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=",
+ "dev": true
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true
+ },
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "istextorbinary": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz",
+ "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==",
+ "dev": true,
+ "requires": {
+ "binaryextensions": "2",
+ "editions": "^1.3.3",
+ "textextensions": "2"
+ }
+ },
+ "isurl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz",
+ "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==",
+ "dev": true,
+ "requires": {
+ "has-to-string-tag-x": "^1.2.0",
+ "is-object": "^1.0.1"
+ }
+ },
+ "jpegtran-bin": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jpegtran-bin/-/jpegtran-bin-4.0.0.tgz",
+ "integrity": "sha512-2cRl1ism+wJUoYAYFt6O/rLBfpXNWG2dUWbgcEkTt5WGMnqI46eEro8T4C5zGROxKRqyKpCBSdHPvt5UYCtxaQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bin-build": "^3.0.0",
+ "bin-wrapper": "^4.0.0",
+ "logalot": "^2.0.0"
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "json-buffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
+ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
+ "dev": true,
+ "optional": true
+ },
+ "json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "jsx-ast-utils": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz",
+ "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3",
+ "object.assign": "^4.1.0"
+ }
+ },
+ "junk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz",
+ "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==",
+ "dev": true
+ },
+ "just-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz",
+ "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=",
+ "dev": true
+ },
+ "keyv": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz",
+ "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "json-buffer": "3.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "last-run": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz",
+ "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=",
+ "dev": true,
+ "requires": {
+ "default-resolution": "^2.0.0",
+ "es6-weak-map": "^2.0.1"
+ }
+ },
+ "lazystream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
+ "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.0.5"
+ }
+ },
+ "lcid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^1.0.0"
+ }
+ },
+ "lead": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz",
+ "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=",
+ "dev": true,
+ "requires": {
+ "flush-write-stream": "^1.0.2"
+ }
+ },
+ "liftoff": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz",
+ "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==",
+ "dev": true,
+ "requires": {
+ "extend": "^3.0.0",
+ "findup-sync": "^3.0.0",
+ "fined": "^1.0.1",
+ "flagged-respawn": "^1.0.0",
+ "is-plain-object": "^2.0.4",
+ "object.map": "^1.0.0",
+ "rechoir": "^0.6.2",
+ "resolve": "^1.1.7"
+ }
+ },
+ "livereload-js": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz",
+ "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==",
+ "dev": true
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0",
+ "strip-bom": "^2.0.0"
+ },
+ "dependencies": {
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
+ "dev": true
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "dev": true,
+ "requires": {
+ "is-utf8": "^0.2.0"
+ }
+ }
+ }
+ },
+ "loader-runner": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+ "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ },
+ "dependencies": {
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "dependencies": {
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ }
+ }
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ },
+ "lodash.assign": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+ "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+ "dev": true
+ },
+ "lodash.unescape": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
+ "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
+ "dev": true
+ },
+ "logalot": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz",
+ "integrity": "sha1-X46MkNME7fElMJUaVVSruMXj9VI=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "figures": "^1.3.5",
+ "squeak": "^1.0.0"
+ },
+ "dependencies": {
+ "figures": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5",
+ "object-assign": "^4.1.0"
+ }
+ }
+ }
+ },
+ "longest": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
+ "dev": true,
+ "optional": true
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "currently-unhandled": "^0.4.1",
+ "signal-exit": "^3.0.0"
+ }
+ },
+ "lowercase-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
+ "dev": true
+ },
+ "lpad-align": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/lpad-align/-/lpad-align-1.1.2.tgz",
+ "integrity": "sha1-IfYArBwwlcPG5JfuZyce4ISB/p4=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "get-stdin": "^4.0.1",
+ "indent-string": "^2.1.0",
+ "longest": "^1.0.0",
+ "meow": "^3.3.0"
+ }
+ },
+ "lru-cache": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+ "dev": true,
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "make-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz",
+ "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==",
+ "dev": true,
+ "requires": {
+ "semver": "^6.0.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "make-iterator": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
+ "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.2"
+ }
+ },
+ "mamacro": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
+ "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==",
+ "dev": true
+ },
+ "map-age-cleaner": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+ "dev": true,
+ "requires": {
+ "p-defer": "^1.0.0"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+ "dev": true
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+ "dev": true
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "matchdep": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
+ "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=",
+ "dev": true,
+ "requires": {
+ "findup-sync": "^2.0.0",
+ "micromatch": "^3.0.4",
+ "resolve": "^1.4.0",
+ "stack-trace": "0.0.10"
+ },
+ "dependencies": {
+ "findup-sync": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
+ "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
+ "dev": true,
+ "requires": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^3.1.0",
+ "micromatch": "^3.0.4",
+ "resolve-dir": "^1.0.1"
+ }
+ }
+ }
+ },
+ "md5.js": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "mdn-data": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
+ "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==",
+ "dev": true,
+ "optional": true
+ },
+ "mem": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
+ "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
+ "dev": true,
+ "requires": {
+ "map-age-cleaner": "^0.1.1",
+ "mimic-fn": "^2.0.0",
+ "p-is-promise": "^2.0.0"
+ },
+ "dependencies": {
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true
+ },
+ "p-is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
+ "dev": true
+ }
+ }
+ },
+ "memoize-one": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
+ "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
+ },
+ "memory-fs": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "meow": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "camelcase-keys": "^2.0.0",
+ "decamelize": "^1.1.2",
+ "loud-rejection": "^1.0.0",
+ "map-obj": "^1.0.1",
+ "minimist": "^1.1.3",
+ "normalize-package-data": "^2.3.4",
+ "object-assign": "^4.0.1",
+ "read-pkg-up": "^1.0.1",
+ "redent": "^1.0.0",
+ "trim-newlines": "^1.0.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "merge2": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz",
+ "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ }
+ },
+ "mime-db": {
+ "version": "1.41.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.41.0.tgz",
+ "integrity": "sha512-B5gxBI+2K431XW8C2rcc/lhppbuji67nf9v39eH8pkWoZDxnAL0PxdpH32KYRScniF8qDHBDlI+ipgg5WrCUYw==",
+ "dev": true
+ },
+ "mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mississippi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+ "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+ "dev": true,
+ "requires": {
+ "concat-stream": "^1.5.0",
+ "duplexify": "^3.4.2",
+ "end-of-stream": "^1.1.0",
+ "flush-write-stream": "^1.0.0",
+ "from2": "^2.1.0",
+ "parallel-transform": "^1.1.0",
+ "pump": "^3.0.0",
+ "pumpify": "^1.3.3",
+ "stream-each": "^1.1.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "mixin-deep": {
+ "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",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "move-concurrently": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "copy-concurrently": "^1.0.0",
+ "fs-write-stream-atomic": "^1.0.8",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.3"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "mute-stdout": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz",
+ "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+ "dev": true,
+ "optional": true
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ }
+ },
+ "neo-async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+ "dev": true
+ },
+ "next-tick": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "node-libs-browser": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+ "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+ "dev": true,
+ "requires": {
+ "assert": "^1.1.1",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^4.3.0",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "^1.0.0",
+ "crypto-browserify": "^3.11.0",
+ "domain-browser": "^1.1.1",
+ "events": "^3.0.0",
+ "https-browserify": "^1.0.0",
+ "os-browserify": "^0.3.0",
+ "path-browserify": "0.0.1",
+ "process": "^0.11.10",
+ "punycode": "^1.2.4",
+ "querystring-es3": "^0.2.0",
+ "readable-stream": "^2.3.3",
+ "stream-browserify": "^2.0.1",
+ "stream-http": "^2.7.2",
+ "string_decoder": "^1.0.0",
+ "timers-browserify": "^2.0.4",
+ "tty-browserify": "0.0.0",
+ "url": "^0.11.0",
+ "util": "^0.11.0",
+ "vm-browserify": "^1.0.1"
+ },
+ "dependencies": {
+ "buffer": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ }
+ },
+ "events": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
+ "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
+ "dev": true
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.3.6",
+ "to-arraybuffer": "^1.0.0",
+ "xtend": "^4.0.0"
+ }
+ },
+ "timers-browserify": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+ "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+ "dev": true,
+ "requires": {
+ "setimmediate": "^1.0.4"
+ }
+ },
+ "tty-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+ "dev": true
+ },
+ "util": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+ "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ }
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "is-builtin-module": "^1.0.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ },
+ "normalize-url": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
+ "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "prepend-http": "^2.0.0",
+ "query-string": "^5.0.1",
+ "sort-keys": "^2.0.0"
+ },
+ "dependencies": {
+ "prepend-http": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
+ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
+ "dev": true,
+ "optional": true
+ },
+ "sort-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
+ "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-plain-obj": "^1.0.0"
+ }
+ }
+ }
+ },
+ "now-and-later": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz",
+ "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.2"
+ }
+ },
+ "npm-conf": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz",
+ "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==",
+ "dev": true,
+ "requires": {
+ "config-chain": "^1.1.11",
+ "pify": "^3.0.0"
+ }
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "nth-check": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
+ "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "boolbase": "~1.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "dev": true,
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-inspect": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
+ "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==",
+ "dev": true
+ },
+ "object-keys": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
+ "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==",
+ "dev": true
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "has-symbols": "^1.0.0",
+ "object-keys": "^1.0.11"
+ }
+ },
+ "object.defaults": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz",
+ "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=",
+ "dev": true,
+ "requires": {
+ "array-each": "^1.0.1",
+ "array-slice": "^1.0.0",
+ "for-own": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
+ "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "object.fromentries": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz",
+ "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.11.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.1"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.5.1"
+ }
+ },
+ "object.map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
+ "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=",
+ "dev": true,
+ "requires": {
+ "for-own": "^1.0.0",
+ "make-iterator": "^1.0.0"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "object.reduce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz",
+ "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=",
+ "dev": true,
+ "requires": {
+ "for-own": "^1.0.0",
+ "make-iterator": "^1.0.0"
+ }
+ },
+ "object.values": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz",
+ "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optipng-bin": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-6.0.0.tgz",
+ "integrity": "sha512-95bB4y8IaTsa/8x6QH4bLUuyvyOoGBCLDA7wOgDL8UFqJpSUh1Hob8JRJhit+wC1ZLN3tQ7mFt7KuBj0x8F2Wg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bin-build": "^3.0.0",
+ "bin-wrapper": "^4.0.0",
+ "logalot": "^2.0.0"
+ }
+ },
+ "ordered-read-streams": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz",
+ "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "os-filter-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz",
+ "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "arch": "^2.1.0"
+ }
+ },
+ "os-locale": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+ "dev": true,
+ "requires": {
+ "lcid": "^1.0.0"
+ }
+ },
+ "p-cancelable": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz",
+ "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==",
+ "dev": true,
+ "optional": true
+ },
+ "p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+ "dev": true
+ },
+ "p-event": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz",
+ "integrity": "sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "p-timeout": "^1.1.1"
+ }
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true
+ },
+ "p-is-promise": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
+ "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
+ "dev": true,
+ "optional": true
+ },
+ "p-limit": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
+ "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-map": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
+ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
+ "dev": true,
+ "requires": {
+ "aggregate-error": "^3.0.0"
+ }
+ },
+ "p-map-series": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz",
+ "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "p-reduce": "^1.0.0"
+ }
+ },
+ "p-pipe": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-3.0.0.tgz",
+ "integrity": "sha512-gwwdRFmaxsT3IU+Tl3vYKVRdjfhg8Bbdjw7B+E0y6F7Yz6l+eaQLn0BRmGMXIhcPDONPtOkMoNwx1etZh4zPJA==",
+ "dev": true
+ },
+ "p-reduce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz",
+ "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=",
+ "dev": true,
+ "optional": true
+ },
+ "p-timeout": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz",
+ "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=",
+ "dev": true,
+ "requires": {
+ "p-finally": "^1.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
+ "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==",
+ "dev": true
+ },
+ "parallel-transform": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+ "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+ "dev": true,
+ "requires": {
+ "cyclist": "^1.0.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.1.5"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
+ "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^4.0.0",
+ "browserify-aes": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.0",
+ "pbkdf2": "^3.0.3",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "parse-filepath": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz",
+ "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=",
+ "dev": true,
+ "requires": {
+ "is-absolute": "^1.0.0",
+ "map-cache": "^0.2.0",
+ "path-root": "^0.1.1"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "parse-node-version": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.0.tgz",
+ "integrity": "sha512-02GTVHD1u0nWc20n2G7WX/PgdhNFG04j5fi1OkaJzPWLTcf6vh6229Lta1wTmXG/7Dg42tCssgkccVt7qvd8Kg==",
+ "dev": true
+ },
+ "parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+ "dev": true
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+ "dev": true
+ },
+ "path-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+ "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+ "dev": true
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true,
+ "requires": {
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
+ },
+ "path-root": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz",
+ "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=",
+ "dev": true,
+ "requires": {
+ "path-root-regex": "^0.1.0"
+ }
+ },
+ "path-root-regex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz",
+ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=",
+ "dev": true
+ },
+ "path-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+ "dev": true,
+ "requires": {
+ "pify": "^2.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "pbkdf2": {
+ "version": "3.0.17",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+ "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+ "dev": true,
+ "requires": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
+ "dev": true
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "picomatch": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
+ "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
+ "dev": true
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ }
+ }
+ },
+ "plugin-error": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
+ "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^1.0.1",
+ "arr-diff": "^4.0.0",
+ "arr-union": "^3.1.0",
+ "extend-shallow": "^3.0.2"
+ }
+ },
+ "plur": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz",
+ "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==",
+ "dev": true,
+ "requires": {
+ "irregular-plurals": "^2.0.0"
+ }
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
+ "prepend-http": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+ "dev": true,
+ "optional": true
+ },
+ "prettier": {
+ "version": "1.18.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
+ "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
+ "dev": true
+ },
+ "pretty-hrtime": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
+ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+ "dev": true
+ },
+ "promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+ "dev": true
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "16.9.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz",
+ "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw=="
+ }
+ }
+ },
+ "proto-list": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
+ "dev": true
+ },
+ "prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+ "dev": true
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ },
+ "dependencies": {
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ }
+ }
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ },
+ "q": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
+ "dev": true,
+ "optional": true
+ },
+ "qs": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz",
+ "integrity": "sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA=="
+ },
+ "query-string": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
+ "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "decode-uri-component": "^0.2.0",
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ }
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "requires": {
+ "performance-now": "^2.1.0"
+ }
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "raw-body": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz",
+ "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=",
+ "dev": true,
+ "requires": {
+ "bytes": "1",
+ "string_decoder": "0.10"
+ },
+ "dependencies": {
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ }
+ }
+ },
+ "react": {
+ "version": "16.9.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz",
+ "integrity": "sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2"
+ }
+ },
+ "react-dom": {
+ "version": "16.9.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz",
+ "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.15.0"
+ }
+ },
+ "react-input-autosize": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.1.tgz",
+ "integrity": "sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA==",
+ "requires": {
+ "prop-types": "^15.5.8"
+ }
+ },
+ "react-is": {
+ "version": "16.9.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz",
+ "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw=="
+ },
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
+ "react-redux": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz",
+ "integrity": "sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "hoist-non-react-statics": "^3.3.0",
+ "invariant": "^2.2.4",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.9.0"
+ }
+ },
+ "react-select": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.0.5.tgz",
+ "integrity": "sha512-2tBXZ1XSqbk2boMUzSmKXwGl/6W46VkSMSLMy+ShccOVyD1kDTLPwLX7lugISkRMmL0v5BcLtriXOLfYwO0otw==",
+ "requires": {
+ "@babel/runtime": "^7.4.4",
+ "@emotion/cache": "^10.0.9",
+ "@emotion/core": "^10.0.9",
+ "@emotion/css": "^10.0.9",
+ "classnames": "^2.2.5",
+ "memoize-one": "^5.0.0",
+ "prop-types": "^15.6.0",
+ "raf": "^3.4.0",
+ "react-input-autosize": "^2.2.1",
+ "react-transition-group": "^2.2.1"
+ }
+ },
+ "react-transition-group": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
+ "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
+ "requires": {
+ "dom-helpers": "^3.4.0",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^1.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^1.0.0"
+ },
+ "dependencies": {
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+ "dev": true,
+ "requires": {
+ "find-up": "^1.0.0",
+ "read-pkg": "^1.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ },
+ "dependencies": {
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "rechoir": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+ "dev": true,
+ "requires": {
+ "resolve": "^1.1.6"
+ }
+ },
+ "redent": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "indent-string": "^2.1.0",
+ "strip-indent": "^1.0.1"
+ }
+ },
+ "redux": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz",
+ "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "symbol-observable": "^1.2.0"
+ }
+ },
+ "redux-logger": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
+ "integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=",
+ "requires": {
+ "deep-diff": "^0.3.5"
+ }
+ },
+ "redux-thunk": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
+ "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
+ },
+ "regenerator-runtime": {
+ "version": "0.13.3",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+ "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "regexpp": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+ "dev": true
+ },
+ "remove-bom-buffer": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz",
+ "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5",
+ "is-utf8": "^0.2.1"
+ }
+ },
+ "remove-bom-stream": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz",
+ "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=",
+ "dev": true,
+ "requires": {
+ "remove-bom-buffer": "^3.0.0",
+ "safe-buffer": "^5.1.0",
+ "through2": "^2.0.3"
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "repeating": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+ "dev": true,
+ "requires": {
+ "is-finite": "^1.0.0"
+ }
+ },
+ "replace-ext": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
+ "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=",
+ "dev": true
+ },
+ "replace-homedir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz",
+ "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=",
+ "dev": true,
+ "requires": {
+ "homedir-polyfill": "^1.0.1",
+ "is-absolute": "^1.0.0",
+ "remove-trailing-separator": "^1.1.0"
+ }
+ },
+ "replacestream": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz",
+ "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.3",
+ "object-assign": "^4.0.1",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
+ "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.5"
+ }
+ },
+ "resolve-cwd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^3.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true
+ }
+ }
+ },
+ "resolve-dir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+ "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.0",
+ "global-modules": "^1.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
+ },
+ "resolve-options": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz",
+ "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=",
+ "dev": true,
+ "requires": {
+ "value-or-function": "^3.0.0"
+ }
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
+ "responselike": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
+ "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "lowercase-keys": "^1.0.0"
+ }
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "dev": true
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "run-parallel": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
+ "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
+ "dev": true
+ },
+ "run-queue": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1"
+ }
+ },
+ "rxjs": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
+ "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "safe-json-parse": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz",
+ "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true,
+ "optional": true
+ },
+ "scheduler": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz",
+ "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "seek-bzip": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
+ "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=",
+ "dev": true,
+ "requires": {
+ "commander": "~2.8.1"
+ }
+ },
+ "semver": {
+ "version": "4.3.6",
+ "resolved": "http://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
+ "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=",
+ "dev": true
+ },
+ "semver-greatest-satisfied-range": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz",
+ "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=",
+ "dev": true,
+ "requires": {
+ "sver-compat": "^1.5.0"
+ }
+ },
+ "semver-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz",
+ "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==",
+ "dev": true,
+ "optional": true
+ },
+ "semver-truncate": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz",
+ "integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "semver": "^5.3.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "serialize-javascript": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
+ "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
+ "dev": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "set-value": {
+ "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",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "dev": true,
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "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"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "sort-keys": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
+ "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
+ "dev": true,
+ "requires": {
+ "is-plain-obj": "^1.0.0"
+ }
+ },
+ "sort-keys-length": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz",
+ "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=",
+ "dev": true,
+ "requires": {
+ "sort-keys": "^1.0.0"
+ }
+ },
+ "source-list-map": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+ "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+ "dev": true
+ },
+ "sparkles": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz",
+ "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==",
+ "dev": true
+ },
+ "spawn-command": {
+ "version": "0.0.2-1",
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
+ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=",
+ "dev": true
+ },
+ "spdx-correct": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz",
+ "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+ "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz",
+ "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==",
+ "dev": true
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "squeak": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/squeak/-/squeak-1.3.0.tgz",
+ "integrity": "sha1-MwRQN7ZDiLVnZ0uEMiplIQc5FsM=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chalk": "^1.0.0",
+ "console-stream": "^0.1.1",
+ "lpad-align": "^1.0.1"
+ }
+ },
+ "ssri": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+ "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+ "dev": true,
+ "requires": {
+ "figgy-pudding": "^3.5.1"
+ }
+ },
+ "stable": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
+ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
+ "dev": true,
+ "optional": true
+ },
+ "stack-trace": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
+ "dev": true
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "dev": true,
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "stream-browserify": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+ "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.1",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "stream-each": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "stream-exhaust": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz",
+ "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==",
+ "dev": true
+ },
+ "stream-shift": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
+ "dev": true
+ },
+ "strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
+ "dev": true,
+ "optional": true
+ },
+ "string-template": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",
+ "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string.prototype.trimleft": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
+ "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
+ "string.prototype.trimright": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
+ "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+ "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
+ "dev": true
+ }
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-dirs": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz",
+ "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==",
+ "dev": true,
+ "requires": {
+ "is-natural-number": "^4.0.1"
+ }
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "get-stdin": "^4.0.1"
+ }
+ },
+ "strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.2"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true,
+ "optional": true
+ },
+ "sver-compat": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz",
+ "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=",
+ "dev": true,
+ "requires": {
+ "es6-iterator": "^2.0.1",
+ "es6-symbol": "^3.1.1"
+ }
+ },
+ "svgo": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz",
+ "integrity": "sha512-MLfUA6O+qauLDbym+mMZgtXCGRfIxyQoeH6IKVcFslyODEe/ElJNwr0FohQ3xG4C6HK6bk3KYPPXwHVJk3V5NQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "coa": "^2.0.2",
+ "css-select": "^2.0.0",
+ "css-select-base-adapter": "^0.1.1",
+ "css-tree": "1.0.0-alpha.33",
+ "csso": "^3.5.1",
+ "js-yaml": "^3.13.1",
+ "mkdirp": "~0.5.1",
+ "object.values": "^1.1.0",
+ "sax": "~1.2.4",
+ "stable": "^0.1.8",
+ "unquote": "~1.1.1",
+ "util.promisify": "~1.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+ },
+ "tapable": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+ "dev": true
+ },
+ "tar-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
+ "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
+ "dev": true,
+ "requires": {
+ "bl": "^1.0.0",
+ "buffer-alloc": "^1.2.0",
+ "end-of-stream": "^1.0.0",
+ "fs-constants": "^1.0.0",
+ "readable-stream": "^2.3.0",
+ "to-buffer": "^1.1.1",
+ "xtend": "^4.0.0"
+ }
+ },
+ "temp-dir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
+ "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=",
+ "dev": true
+ },
+ "tempfile": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz",
+ "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=",
+ "dev": true,
+ "requires": {
+ "temp-dir": "^1.0.0",
+ "uuid": "^3.0.1"
+ }
+ },
+ "ternary-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz",
+ "integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^4.1.1",
+ "fork-stream": "^0.0.4",
+ "merge-stream": "^2.0.0",
+ "through2": "^3.0.1"
+ },
+ "dependencies": {
+ "duplexify": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz",
+ "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.4.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
+ "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "through2": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
+ "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "2 || 3"
+ }
+ }
+ }
+ },
+ "terser": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.2.tgz",
+ "integrity": "sha512-obxk4x19Zlzj9zY4QeXj9iPCb5W8YGn4v3pn4/fHj0Nw8+R7N02Kvwvz9VpOItCZZD8RC+vnYCDL0gP6FAJ7Xg==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
+ "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "terser-webpack-plugin": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
+ "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
+ "dev": true,
+ "requires": {
+ "cacache": "^12.0.2",
+ "find-cache-dir": "^2.1.0",
+ "is-wsl": "^1.1.0",
+ "schema-utils": "^1.0.0",
+ "serialize-javascript": "^1.7.0",
+ "source-map": "^0.6.1",
+ "terser": "^4.1.2",
+ "webpack-sources": "^1.4.0",
+ "worker-farm": "^1.7.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "textextensions": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.4.0.tgz",
+ "integrity": "sha512-qftQXnX1DzpSV8EddtHIT0eDDEiBF8ywhFYR2lI9xrGtxqKN+CvLXhACeCIGbCpQfxxERbrkZEFb8cZcDKbVZA==",
+ "dev": true
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
+ "through2-concurrent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/through2-concurrent/-/through2-concurrent-2.0.0.tgz",
+ "integrity": "sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==",
+ "dev": true,
+ "requires": {
+ "through2": "^2.0.0"
+ }
+ },
+ "through2-filter": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz",
+ "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==",
+ "dev": true,
+ "requires": {
+ "through2": "~2.0.0",
+ "xtend": "~4.0.0"
+ }
+ },
+ "time-stamp": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
+ "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=",
+ "dev": true
+ },
+ "timed-out": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
+ "dev": true
+ },
+ "tiny-lr": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz",
+ "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==",
+ "dev": true,
+ "requires": {
+ "body": "^5.1.0",
+ "debug": "^3.1.0",
+ "faye-websocket": "~0.10.0",
+ "livereload-js": "^2.3.0",
+ "object-assign": "^4.1.0",
+ "qs": "^6.4.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
+ "to-absolute-glob": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
+ "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=",
+ "dev": true,
+ "requires": {
+ "is-absolute": "^1.0.0",
+ "is-negated-glob": "^1.0.0"
+ }
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "to-buffer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
+ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==",
+ "dev": true
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "to-through": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz",
+ "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=",
+ "dev": true,
+ "requires": {
+ "through2": "^2.0.3"
+ }
+ },
+ "tree-kill": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz",
+ "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==",
+ "dev": true
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+ "dev": true,
+ "optional": true
+ },
+ "trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.2"
+ }
+ },
+ "ts-loader": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.1.2.tgz",
+ "integrity": "sha512-dudxFKm0Ellrg/gLNlu+97/UgwvoMK0SdUVImPUSzq3IcRUVtShylZvcMX+CgvCQL1BEKb913NL0gAP1GA/OFw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.3.0",
+ "enhanced-resolve": "^4.0.0",
+ "loader-utils": "^1.0.2",
+ "micromatch": "^4.0.0",
+ "semver": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
+ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.1",
+ "picomatch": "^2.0.5"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "tslib": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
+ "dev": true
+ },
+ "tsutils": {
+ "version": "3.17.1",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
+ "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "type": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+ "dev": true
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "typescript": {
+ "version": "3.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
+ "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
+ "dev": true
+ },
+ "unbzip2-stream": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz",
+ "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==",
+ "dev": true,
+ "requires": {
+ "buffer": "^5.2.1",
+ "through": "^2.3.8"
+ }
+ },
+ "unc-path-regex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
+ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
+ "dev": true
+ },
+ "undertaker": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz",
+ "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.0.1",
+ "arr-map": "^2.0.0",
+ "bach": "^1.0.0",
+ "collection-map": "^1.0.0",
+ "es6-weak-map": "^2.0.1",
+ "last-run": "^1.1.0",
+ "object.defaults": "^1.0.0",
+ "object.reduce": "^1.0.0",
+ "undertaker-registry": "^1.0.0"
+ }
+ },
+ "undertaker-registry": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz",
+ "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=",
+ "dev": true
+ },
+ "union-value": {
+ "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": "^2.0.1"
+ }
+ },
+ "unique-filename": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+ "dev": true,
+ "requires": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "unique-slug": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "unique-stream": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz",
+ "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==",
+ "dev": true,
+ "requires": {
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "through2-filter": "^3.0.0"
+ }
+ },
+ "unquote": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
+ "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=",
+ "dev": true,
+ "optional": true
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ }
+ }
+ },
+ "upath": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ }
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "url-parse-lax": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+ "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "prepend-http": "^1.0.1"
+ }
+ },
+ "url-to-options": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
+ "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=",
+ "dev": true
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+ "dev": true
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "util.promisify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
+ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "object.getownpropertydescriptors": "^2.0.3"
+ }
+ },
+ "uuid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+ "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
+ "dev": true
+ },
+ "v8flags": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz",
+ "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==",
+ "dev": true,
+ "requires": {
+ "homedir-polyfill": "^1.0.1"
+ }
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "value-or-function": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz",
+ "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=",
+ "dev": true
+ },
+ "vinyl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz",
+ "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==",
+ "dev": true,
+ "requires": {
+ "clone": "^2.1.1",
+ "clone-buffer": "^1.0.0",
+ "clone-stats": "^1.0.0",
+ "cloneable-readable": "^1.0.0",
+ "remove-trailing-separator": "^1.0.1",
+ "replace-ext": "^1.0.0"
+ }
+ },
+ "vinyl-fs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz",
+ "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==",
+ "dev": true,
+ "requires": {
+ "fs-mkdirp-stream": "^1.0.0",
+ "glob-stream": "^6.1.0",
+ "graceful-fs": "^4.0.0",
+ "is-valid-glob": "^1.0.0",
+ "lazystream": "^1.0.0",
+ "lead": "^1.0.0",
+ "object.assign": "^4.0.4",
+ "pumpify": "^1.3.5",
+ "readable-stream": "^2.3.3",
+ "remove-bom-buffer": "^3.0.0",
+ "remove-bom-stream": "^1.2.0",
+ "resolve-options": "^1.1.0",
+ "through2": "^2.0.0",
+ "to-through": "^2.0.0",
+ "value-or-function": "^3.0.0",
+ "vinyl": "^2.0.0",
+ "vinyl-sourcemap": "^1.1.0"
+ }
+ },
+ "vinyl-sourcemap": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz",
+ "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=",
+ "dev": true,
+ "requires": {
+ "append-buffer": "^1.0.2",
+ "convert-source-map": "^1.5.0",
+ "graceful-fs": "^4.1.6",
+ "normalize-path": "^2.1.1",
+ "now-and-later": "^2.0.0",
+ "remove-bom-buffer": "^3.0.0",
+ "vinyl": "^2.0.0"
+ }
+ },
+ "vm-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz",
+ "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
+ "dev": true
+ },
+ "watchpack": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
+ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^2.0.2",
+ "graceful-fs": "^4.1.2",
+ "neo-async": "^2.5.0"
+ }
+ },
+ "webpack": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.0.tgz",
+ "integrity": "sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.8.5",
+ "@webassemblyjs/helper-module-context": "1.8.5",
+ "@webassemblyjs/wasm-edit": "1.8.5",
+ "@webassemblyjs/wasm-parser": "1.8.5",
+ "acorn": "^6.2.1",
+ "ajv": "^6.10.2",
+ "ajv-keywords": "^3.4.1",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^4.1.0",
+ "eslint-scope": "^4.0.3",
+ "json-parse-better-errors": "^1.0.2",
+ "loader-runner": "^2.4.0",
+ "loader-utils": "^1.2.3",
+ "memory-fs": "^0.4.1",
+ "micromatch": "^3.1.10",
+ "mkdirp": "^0.5.1",
+ "neo-async": "^2.6.1",
+ "node-libs-browser": "^2.2.1",
+ "schema-utils": "^1.0.0",
+ "tapable": "^1.1.3",
+ "terser-webpack-plugin": "^1.4.1",
+ "watchpack": "^1.6.0",
+ "webpack-sources": "^1.4.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
+ "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+ "dev": true
+ },
+ "eslint-scope": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+ "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ }
+ }
+ },
+ "webpack-cli": {
+ "version": "3.3.9",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.9.tgz",
+ "integrity": "sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.2",
+ "cross-spawn": "6.0.5",
+ "enhanced-resolve": "4.1.0",
+ "findup-sync": "3.0.0",
+ "global-modules": "2.0.0",
+ "import-local": "2.0.0",
+ "interpret": "1.2.0",
+ "loader-utils": "1.2.3",
+ "supports-color": "6.1.0",
+ "v8-compile-cache": "2.0.3",
+ "yargs": "13.2.4"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ }
+ },
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^3.0.0"
+ }
+ },
+ "global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dev": true,
+ "requires": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ }
+ },
+ "invert-kv": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "lcid": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^2.0.0"
+ }
+ },
+ "os-locale": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+ "dev": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "lcid": "^2.0.0",
+ "mem": "^4.0.0"
+ }
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "v8-compile-cache": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
+ "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
+ "dev": true
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ }
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "13.2.4",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz",
+ "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==",
+ "dev": true,
+ "requires": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "os-locale": "^3.1.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.0"
+ }
+ },
+ "yargs-parser": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "webpack-sources": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+ "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+ "dev": true,
+ "requires": {
+ "source-list-map": "^2.0.0",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "websocket-driver": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
+ "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
+ "dev": true,
+ "requires": {
+ "http-parser-js": ">=0.4.0 <0.4.11",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+ "dev": true
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+ "dev": true
+ },
+ "worker-farm": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+ "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+ "dev": true,
+ "requires": {
+ "errno": "~0.1.7"
+ }
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+ "dev": true
+ },
+ "y18n": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+ "dev": true
+ },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
+ "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^3.0.0",
+ "cliui": "^3.2.0",
+ "decamelize": "^1.1.1",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^1.4.0",
+ "read-pkg-up": "^1.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^1.0.2",
+ "which-module": "^1.0.0",
+ "y18n": "^3.2.1",
+ "yargs-parser": "^5.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+ "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^3.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ }
+ }
+ },
+ "yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
+ "dev": true,
+ "requires": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "yazl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz",
+ "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==",
+ "dev": true,
+ "requires": {
+ "buffer-crc32": "~0.2.3"
+ }
+ }
+ }
+}
diff --git a/browser/package.json b/browser/package.json
new file mode 100644
index 00000000..78015c03
--- /dev/null
+++ b/browser/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "dnote-extension",
+ "description": "Dnote browser extension for Chrome and Firefox",
+ "scripts": {
+ "clean": "TARGET=firefox gulp clean && TARGET=chrome gulp clean",
+ "build:chrome": "TARGET=chrome NODE_ENV=production concurrently webpack \"gulp build\"",
+ "build:firefox": "TARGET=firefox NODE_ENV=production concurrently webpack \"gulp build\"",
+ "package:chrome": "TARGET=chrome NODE_ENV=production gulp package",
+ "package:firefox": "TARGET=firefox NODE_ENV=production gulp package",
+ "watch:chrome": "TARGET=chrome NODE_ENV=development concurrently \"webpack --watch\" \"gulp watch\" ",
+ "watch:firefox": "TARGET=firefox NODE_ENV=development concurrently \"webpack --watch\" \"gulp watch\" "
+ },
+ "author": "Monomax Software Pty Ltd",
+ "license": "GPL-3.0-or-later",
+ "version": "1.0.0",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "lodash": "^4.17.15",
+ "qs": "^6.9.0",
+ "react": "^16.9.0",
+ "react-dom": "^16.9.0",
+ "react-redux": "^7.0.0",
+ "react-select": "^3.0.0",
+ "redux": "^4.0.4",
+ "redux-logger": "^3.0.6",
+ "redux-thunk": "^2.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^16.9.3",
+ "@types/react-dom": "^16.9.1",
+ "@typescript-eslint/eslint-plugin": "^2.3.1",
+ "@typescript-eslint/parser": "^2.3.1",
+ "concurrently": "^4.1.2",
+ "del": "^5.0.0",
+ "eslint-config-airbnb": "^18.0.1",
+ "eslint-plugin-import": "^2.18.2",
+ "eslint-plugin-jsx-a11y": "^6.2.3",
+ "eslint-plugin-react": "^7.14.3",
+ "eslint-plugin-react-hooks": "^2.0.1",
+ "gulp": "^4.0.0",
+ "gulp-if": "^3.0.0",
+ "gulp-imagemin": "^6.0.0",
+ "gulp-livereload": "^4.0.2",
+ "gulp-replace": "^1.0.0",
+ "gulp-zip": "^5.0.0",
+ "prettier": "^1.18.2",
+ "ts-loader": "^6.1.2",
+ "typescript": "^3.6.3",
+ "webpack": "^4.41.0",
+ "webpack-cli": "^3.3.9"
+ }
+}
diff --git a/browser/scripts/build_prod.sh b/browser/scripts/build_prod.sh
new file mode 100755
index 00000000..24050ca3
--- /dev/null
+++ b/browser/scripts/build_prod.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# build_prod.sh builds distributable archive for the addon
+# remember to bump version in package.json
+set -eux
+
+# clean
+npm run clean
+
+# chrome
+npm run build:chrome
+npm run package:chrome
+# firefox
+npm run build:firefox
+npm run package:firefox
diff --git a/browser/scripts/zip.sh b/browser/scripts/zip.sh
new file mode 100755
index 00000000..3b250894
--- /dev/null
+++ b/browser/scripts/zip.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+tar --exclude='./node_modules' --exclude='./package' --exclude='./dist' -zcvf extension.tar.gz * .babelrc .eslintrc
diff --git a/browser/src/browser.d.ts b/browser/src/browser.d.ts
new file mode 100644
index 00000000..62aaf801
--- /dev/null
+++ b/browser/src/browser.d.ts
@@ -0,0 +1,3 @@
+// browser.d.ts
+declare var browser: any;
+declare var chrome: any;
diff --git a/browser/src/global.d.ts b/browser/src/global.d.ts
new file mode 100644
index 00000000..69777464
--- /dev/null
+++ b/browser/src/global.d.ts
@@ -0,0 +1,6 @@
+// global.d.ts
+
+// defined by webpack-define-plugin
+declare var __API_ENDPOINT__: string;
+declare var __WEB_URL__: string;
+declare var __VERSION__: string;
diff --git a/browser/src/images/close.svg b/browser/src/images/close.svg
new file mode 100644
index 00000000..1e675311
--- /dev/null
+++ b/browser/src/images/close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/browser/src/images/hamberger-menu.svg b/browser/src/images/hamberger-menu.svg
new file mode 100644
index 00000000..1dd2b78f
--- /dev/null
+++ b/browser/src/images/hamberger-menu.svg
@@ -0,0 +1 @@
+
diff --git a/browser/src/images/iconx128.png b/browser/src/images/iconx128.png
new file mode 100644
index 00000000..011a9d8f
Binary files /dev/null and b/browser/src/images/iconx128.png differ
diff --git a/browser/src/images/iconx16.png b/browser/src/images/iconx16.png
new file mode 100644
index 00000000..3e8be0fe
Binary files /dev/null and b/browser/src/images/iconx16.png differ
diff --git a/browser/src/images/iconx32.png b/browser/src/images/iconx32.png
new file mode 100644
index 00000000..ea23c53e
Binary files /dev/null and b/browser/src/images/iconx32.png differ
diff --git a/browser/src/images/iconx48.png b/browser/src/images/iconx48.png
new file mode 100644
index 00000000..1d27086f
Binary files /dev/null and b/browser/src/images/iconx48.png differ
diff --git a/browser/src/images/iconx96.png b/browser/src/images/iconx96.png
new file mode 100644
index 00000000..a7508afd
Binary files /dev/null and b/browser/src/images/iconx96.png differ
diff --git a/browser/src/images/logo-circle.png b/browser/src/images/logo-circle.png
new file mode 100644
index 00000000..54022b6f
Binary files /dev/null and b/browser/src/images/logo-circle.png differ
diff --git a/browser/src/popup.html b/browser/src/popup.html
new file mode 100644
index 00000000..861f45d1
--- /dev/null
+++ b/browser/src/popup.html
@@ -0,0 +1,15 @@
+
+
+
+ Dnote browser extension
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/src/scripts/components/App.tsx b/browser/src/scripts/components/App.tsx
new file mode 100644
index 00000000..1fd3a928
--- /dev/null
+++ b/browser/src/scripts/components/App.tsx
@@ -0,0 +1,88 @@
+import React, { useState, useEffect } from 'react';
+import classnames from 'classnames';
+
+import services from '../utils/services';
+import { resetSettings } from '../store/settings/actions';
+import { useSelector, useDispatch } from '../store/hooks';
+import Header from './Header';
+import Home from './Home';
+import Menu from './Menu';
+import Success from './Success';
+import Composer from './Composer';
+
+interface Props {}
+
+function renderRoutes(path: string, isLoggedIn: boolean) {
+ switch (path) {
+ case '/success':
+ return ;
+ case '/':
+ if (isLoggedIn) {
+ return ;
+ }
+
+ return ;
+ default:
+ return Not found
;
+ }
+}
+
+const App: React.FunctionComponent = () => {
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+ const [errMsg, setErrMsg] = useState('');
+
+ const dispatch = useDispatch();
+ const { path, settings } = useSelector(state => {
+ return {
+ path: state.location.path,
+ settings: state.settings
+ };
+ });
+
+ useEffect(() => {
+ // if session is expired, clear it
+ const now = Math.round(new Date().getTime() / 1000);
+ if (settings.sessionKey && settings.sessionKeyExpiry < now) {
+ dispatch(resetSettings());
+ }
+ }, [dispatch]);
+
+ const isLoggedIn = Boolean(settings.sessionKey);
+ const toggleMenu = () => {
+ setIsMenuOpen(!isMenuOpen);
+ };
+ const handleLogout = async (done?: Function) => {
+ try {
+ await services.users.signout();
+ dispatch(resetSettings());
+
+ if (done) {
+ done();
+ }
+ } catch (e) {
+ setErrMsg(e.message);
+ }
+ };
+
+ return (
+
+
+
+ {isMenuOpen && (
+
+ )}
+
+
+ {errMsg && {errMsg}
}
+
+ {renderRoutes(path, isLoggedIn)}
+
+
+ );
+};
+
+export default App;
diff --git a/browser/src/scripts/components/BookIcon.tsx b/browser/src/scripts/components/BookIcon.tsx
new file mode 100644
index 00000000..0245f8ad
--- /dev/null
+++ b/browser/src/scripts/components/BookIcon.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+const Icon = ({ fill, width, height, className }) => {
+ const h = `${height}px`;
+ const w = `${width}px`;
+
+ return (
+
+ );
+};
+
+Icon.defaultProps = {
+ fill: '#000',
+ width: 32,
+ height: 32
+};
+
+export default Icon;
diff --git a/browser/src/scripts/components/BookSelector.tsx b/browser/src/scripts/components/BookSelector.tsx
new file mode 100644
index 00000000..b37a56a5
--- /dev/null
+++ b/browser/src/scripts/components/BookSelector.tsx
@@ -0,0 +1,110 @@
+import React, { useEffect } from 'react';
+import CreatableSelect from 'react-select/creatable';
+import cloneDeep from 'lodash/cloneDeep';
+import { useSelector, useDispatch } from '../store/hooks';
+import { updateBook, resetBook } from '../store/composer/actions';
+
+import BookIcon from './BookIcon';
+
+interface Props {
+ selectorRef: React.Dispatch;
+ onAfterChange: () => void;
+}
+
+function useCurrentOptions(options) {
+ const currentValue = useSelector(state => {
+ return state.composer.bookUUID;
+ });
+
+ for (let i = 0; i < options.length; i++) {
+ const option = options[i];
+
+ if (option.value === currentValue) {
+ return option;
+ }
+ }
+
+ return null;
+}
+
+function useOptions() {
+ const { books, composer } = useSelector(state => {
+ return {
+ books: state.books,
+ composer: state.composer
+ };
+ });
+
+ const opts = books.items.map(book => {
+ return {
+ label: book.label,
+ value: book.uuid
+ };
+ });
+
+ if (composer.bookLabel !== '' && composer.bookUUID === '') {
+ opts.push({
+ label: composer.bookLabel,
+ value: ''
+ });
+ }
+
+ // clone the array so as not to mutate Redux state manually
+ // e.g. react-select mutates options prop internally upon adding a new option
+ return cloneDeep(opts);
+}
+
+const BookSelector: React.FunctionComponent = ({
+ selectorRef,
+ onAfterChange
+}) => {
+ const dispatch = useDispatch();
+ const { books, composer } = useSelector(state => {
+ return {
+ books: state.books,
+ composer: state.composer
+ };
+ });
+ const options = useOptions();
+ const currentOption = useCurrentOptions(options);
+
+ let placeholder: string;
+ if (books.isFetched) {
+ placeholder = 'Choose a book';
+ } else {
+ placeholder = 'Loading books...';
+ }
+
+ return (
+ {
+ selectorRef(el);
+ }}
+ multi={false}
+ isClearable
+ placeholder={placeholder}
+ options={options}
+ value={currentOption}
+ onChange={(option, meta) => {
+ if (meta.action === 'clear') {
+ dispatch(resetBook());
+ } else {
+ let uuid: string;
+ if (meta.action === 'create-option') {
+ uuid = '';
+ } else {
+ uuid = option.value;
+ }
+
+ dispatch(updateBook({ uuid, label: option.label }));
+ }
+
+ onAfterChange();
+ }}
+ formatCreateLabel={label => `Add a new book ${label}`}
+ isDisabled={!books.isFetched}
+ />
+ );
+};
+
+export default BookSelector;
diff --git a/browser/src/scripts/components/CloseIcon.tsx b/browser/src/scripts/components/CloseIcon.tsx
new file mode 100644
index 00000000..94a9f0c0
--- /dev/null
+++ b/browser/src/scripts/components/CloseIcon.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+export default () => (
+
+);
diff --git a/browser/src/scripts/components/Composer.tsx b/browser/src/scripts/components/Composer.tsx
new file mode 100644
index 00000000..b15b8dcf
--- /dev/null
+++ b/browser/src/scripts/components/Composer.tsx
@@ -0,0 +1,212 @@
+import React, { useState, useEffect, useRef } from 'react';
+import classnames from 'classnames';
+
+import { KEYCODE_ENTER } from 'jslib/helpers/keyboard';
+import services from '../utils/services';
+import BookSelector from './BookSelector';
+import Flash from './Flash';
+import { useSelector, useDispatch } from '../store/hooks';
+import { updateContent, resetComposer } from '../store/composer/actions';
+import { fetchBooks } from '../store/books/actions';
+import { navigate } from '../store/location/actions';
+
+interface Props {}
+
+// focusBookSelectorInput focuses on the input element of the book selector.
+// It needs to traverse the tree returned by the ref API of the 'react-select' library,
+// and to guard against possible breaking changes, if the path does not exist, it noops.
+function focusBookSelectorInput(bookSelectorRef) {
+ bookSelectorRef.select &&
+ bookSelectorRef.select.select &&
+ bookSelectorRef.select.select.inputRef &&
+ bookSelectorRef.select.select.inputRef.focus();
+}
+
+function useFetchData() {
+ const dispatch = useDispatch();
+
+ const { books } = useSelector(state => {
+ return {
+ books: state.books
+ };
+ });
+
+ useEffect(() => {
+ if (!books.isFetched) {
+ dispatch(fetchBooks());
+ }
+ }, [dispatch, books.isFetched]);
+}
+
+function useInitFocus(contentRef, bookSelectorRef) {
+ const { composer, books } = useSelector(state => {
+ return {
+ composer: state.composer,
+ books: state.books
+ };
+ });
+
+ useEffect(() => {
+ if (!books.isFetched) {
+ return () => null;
+ }
+
+ if (bookSelectorRef && contentRef) {
+ if (composer.bookLabel === '') {
+ focusBookSelectorInput(bookSelectorRef);
+ } else {
+ contentRef.focus();
+ }
+ }
+ }, [contentRef, bookSelectorRef, books.isFetched]);
+}
+
+const Composer: React.FunctionComponent = () => {
+ useFetchData();
+ const [contentFocused, setContentFocused] = useState(false);
+ const [submitting, setSubmitting] = useState(false);
+ const [errMsg, setErrMsg] = useState('');
+ const dispatch = useDispatch();
+ const [contentRef, setContentEl] = useState(null);
+ const [bookSelectorRef, setBookSelectorEl] = useState(null);
+
+ const { composer, settings } = useSelector(state => {
+ return {
+ composer: state.composer,
+ settings: state.settings
+ };
+ });
+
+ const handleSubmit = async e => {
+ e.preventDefault();
+
+ setSubmitting(true);
+
+ try {
+ let bookUUID;
+ if (composer.bookUUID === '') {
+ const resp = await services.books.create(
+ {
+ name: composer.bookLabel
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${settings.sessionKey}`
+ }
+ }
+ );
+
+ bookUUID = resp.book.uuid;
+ } else {
+ bookUUID = composer.bookUUID;
+ }
+
+ const resp = await services.notes.create(
+ {
+ book_uuid: bookUUID,
+ content: composer.content
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${settings.sessionKey}`
+ }
+ }
+ );
+
+ // clear the composer state
+ setErrMsg('');
+ setSubmitting(false);
+
+ dispatch(resetComposer());
+
+ // navigate
+ dispatch(
+ navigate('/success', {
+ bookName: composer.bookLabel,
+ noteUUID: resp.result.uuid
+ })
+ );
+ } catch (e) {
+ setErrMsg(e.message);
+ setSubmitting(false);
+ }
+ };
+
+ const handleSubmitShortcut = e => {
+ // Shift + Enter
+ if (e.shiftKey && e.keyCode === KEYCODE_ENTER) {
+ handleSubmit(e);
+ }
+ };
+
+ useEffect(() => {
+ window.addEventListener('keydown', handleSubmitShortcut);
+
+ return () => {
+ window.removeEventListener('keydown', handleSubmitShortcut);
+ };
+ }, []);
+
+ useEffect(() => {}, []);
+
+ let submitBtnText: string;
+ if (submitting) {
+ submitBtnText = 'Saving...';
+ } else {
+ submitBtnText = 'Save';
+ }
+
+ useInitFocus(contentRef, bookSelectorRef);
+
+ return (
+
+ );
+};
+
+export default Composer;
diff --git a/browser/src/scripts/components/Flash.tsx b/browser/src/scripts/components/Flash.tsx
new file mode 100644
index 00000000..c70c9b10
--- /dev/null
+++ b/browser/src/scripts/components/Flash.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+interface Props {
+ message: string;
+ when: boolean;
+}
+
+const Flash: React.FunctionComponent = ({ message, when }) => {
+ if (when) {
+ return Error: {message}
;
+ }
+
+ return null;
+};
+
+export default Flash;
diff --git a/browser/src/scripts/components/Header.tsx b/browser/src/scripts/components/Header.tsx
new file mode 100644
index 00000000..8fe8833b
--- /dev/null
+++ b/browser/src/scripts/components/Header.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import Link from './Link';
+import MenuToggleIcon from './MenuToggleIcon';
+import CloseIcon from './CloseIcon';
+
+interface Props {
+ toggleMenu: () => void;
+ isShowingMenu: boolean;
+}
+
+const Header: React.FunctionComponent = ({
+ toggleMenu,
+ isShowingMenu
+}) => (
+
+);
+
+export default Header;
diff --git a/browser/src/scripts/components/Home.tsx b/browser/src/scripts/components/Home.tsx
new file mode 100644
index 00000000..f4d20351
--- /dev/null
+++ b/browser/src/scripts/components/Home.tsx
@@ -0,0 +1,100 @@
+import React, { useState } from 'react';
+import { connect } from 'react-redux';
+import { findDOMNode } from 'react-dom';
+
+import Link from './Link';
+import config from '../utils/config';
+import { updateSettings } from '../store/settings/actions';
+import { useDispatch } from '../store/hooks';
+import services from '../utils/services';
+
+interface Props {}
+
+const Home: React.FunctionComponent = () => {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [errMsg, setErrMsg] = useState('');
+ const [loggingIn, setLoggingIn] = useState(false);
+ const dispatch = useDispatch();
+
+ const handleLogin = async e => {
+ e.preventDefault();
+
+ setErrMsg('');
+ setLoggingIn(true);
+
+ try {
+ const signinResp = await services.users.signin({ email, password });
+
+ dispatch(
+ updateSettings({
+ sessionKey: signinResp.key,
+ sessionKeyExpiry: signinResp.expiresAt
+ })
+ );
+ } catch (e) {
+ console.log('error while logging in', e);
+
+ setErrMsg(e.message);
+ setLoggingIn(false);
+ }
+ };
+
+ return (
+
+
Welcome to Dnote
+
+
A simple personal knowledge base
+
+ {errMsg &&
{errMsg}
}
+
+
+
+
+
+ );
+};
+
+export default Home;
diff --git a/browser/src/scripts/components/Link.tsx b/browser/src/scripts/components/Link.tsx
new file mode 100644
index 00000000..f0e5eaca
--- /dev/null
+++ b/browser/src/scripts/components/Link.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { useDispatch } from '../store/hooks';
+import { navigate } from '../store/location/actions';
+
+interface Props {
+ to: string;
+ className: string;
+ tabIndex?: number;
+ onClick?: () => void;
+}
+
+const Link: React.FunctionComponent = ({
+ to,
+ children,
+ onClick,
+ ...restProps
+}) => {
+ const dispatch = useDispatch();
+
+ return (
+ {
+ e.preventDefault();
+
+ dispatch(navigate(to));
+
+ if (onClick) {
+ onClick();
+ }
+ }}
+ {...restProps}
+ >
+ {children}
+
+ );
+};
+
+export default Link;
diff --git a/browser/src/scripts/components/Menu.tsx b/browser/src/scripts/components/Menu.tsx
new file mode 100644
index 00000000..7ca67f62
--- /dev/null
+++ b/browser/src/scripts/components/Menu.tsx
@@ -0,0 +1,35 @@
+import React, { Fragment } from 'react';
+
+import Link from './Link';
+
+export default ({ toggleMenu, loggedIn, onLogout }) => (
+
+
+
+
+
+);
diff --git a/browser/src/scripts/components/MenuToggleIcon.tsx b/browser/src/scripts/components/MenuToggleIcon.tsx
new file mode 100644
index 00000000..3527c84e
--- /dev/null
+++ b/browser/src/scripts/components/MenuToggleIcon.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+
+export default () => (
+
+);
diff --git a/browser/src/scripts/components/Success.tsx b/browser/src/scripts/components/Success.tsx
new file mode 100644
index 00000000..99d06ebb
--- /dev/null
+++ b/browser/src/scripts/components/Success.tsx
@@ -0,0 +1,85 @@
+import React, { Fragment, useEffect, useState } from 'react';
+
+import {
+ KEYCODE_ENTER,
+ KEYCODE_ESC,
+ KEYCODE_LOWERCASE_B
+} from 'jslib/helpers/keyboard';
+import Flash from './Flash';
+import ext from '../utils/ext';
+import config from '../utils/config';
+import BookIcon from './BookIcon';
+import { navigate } from '../store/location/actions';
+import { useSelector, useDispatch } from '../store/hooks';
+
+const Success: React.FunctionComponent = () => {
+ const [errorMsg, setErrorMsg] = useState('');
+
+ const dispatch = useDispatch();
+ const { location } = useSelector(state => {
+ return {
+ location: state.location
+ };
+ });
+
+ const { bookName, noteUUID } = location.state;
+
+ const handleKeydown = e => {
+ e.preventDefault();
+
+ if (e.keyCode === KEYCODE_ENTER) {
+ dispatch(navigate('/'));
+ } else if (e.keyCode === KEYCODE_ESC) {
+ window.close();
+ } else if (e.keyCode === KEYCODE_LOWERCASE_B) {
+ const url = `${config.webUrl}/notes/${noteUUID}`;
+
+ ext.tabs
+ .create({ url })
+ .then(() => {
+ window.close();
+ })
+ .catch(err => {
+ setErrorMsg(err.message);
+ });
+ }
+ };
+
+ useEffect(() => {
+ window.addEventListener('keydown', handleKeydown);
+
+ return () => {
+ window.removeEventListener('keydown', handleKeydown);
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
Saved to {bookName}
+
+
+
+ -
+ Enter{' '}
+
Go back
+
+ -
+ b{' '}
+
Open in browser
+
+ -
+ ESC
Close
+
+
+
+
+ );
+};
+
+export default Success;
diff --git a/browser/src/scripts/popup.tsx b/browser/src/scripts/popup.tsx
new file mode 100644
index 00000000..537755cb
--- /dev/null
+++ b/browser/src/scripts/popup.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import { createStore, applyMiddleware, compose } from 'redux';
+import thunkMiddleware from 'redux-thunk';
+
+import { debounce } from 'jslib/helpers/perf';
+import configureStore from './store';
+import { loadState, saveState } from './utils/storage';
+import App from './components/App';
+import ext from './utils/ext';
+
+const appContainer = document.getElementById('app');
+
+loadState(items => {
+ if (ext.runtime.lastError) {
+ appContainer.innerText = `Failed to retrieve previous app state ${ext.runtime.lastError.message}`;
+ return;
+ }
+
+ let initialState;
+ const prevState = items.state;
+ if (prevState) {
+ // rehydrate
+ initialState = prevState;
+ }
+
+ const store = configureStore(initialState);
+
+ store.subscribe(
+ debounce(() => {
+ const state = store.getState();
+
+ saveState(state);
+ }, 100)
+ );
+
+ ReactDOM.render(
+
+
+ ,
+ appContainer,
+ () => {
+ // On Chrome, popup window size is kept at minimum if app render is delayed
+ // Therefore add minimum dimension to body until app is rendered
+ document.getElementsByTagName('body')[0].className = '';
+ }
+ );
+});
diff --git a/browser/src/scripts/store/books/actions.ts b/browser/src/scripts/store/books/actions.ts
new file mode 100644
index 00000000..0885a1e0
--- /dev/null
+++ b/browser/src/scripts/store/books/actions.ts
@@ -0,0 +1,59 @@
+import services from '../../utils/services';
+
+import {
+ START_FETCHING,
+ RECEIVE,
+ RECEIVE_ERROR,
+ StartFetchingAction,
+ ReceiveAction,
+ ReceiveErrorAction
+} from './types';
+
+function startFetchingBooks(): StartFetchingAction {
+ return {
+ type: START_FETCHING
+ };
+}
+
+function receiveBooks(books): ReceiveAction {
+ return {
+ type: RECEIVE,
+ data: {
+ books
+ }
+ };
+}
+
+function receiveBooksError(error: string): ReceiveErrorAction {
+ return {
+ type: RECEIVE_ERROR,
+ data: {
+ error
+ }
+ };
+}
+
+export function fetchBooks() {
+ return (dispatch, getState) => {
+ dispatch(startFetchingBooks());
+
+ const { settings } = getState();
+
+ services.books
+ .fetch(
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${settings.sessionKey}`
+ }
+ }
+ )
+ .then(books => {
+ dispatch(receiveBooks(books));
+ })
+ .catch(err => {
+ console.log('error fetching books', err);
+ dispatch(receiveBooksError(err));
+ });
+ };
+}
diff --git a/browser/src/scripts/store/books/reducers.ts b/browser/src/scripts/store/books/reducers.ts
new file mode 100644
index 00000000..cd4b1ed7
--- /dev/null
+++ b/browser/src/scripts/store/books/reducers.ts
@@ -0,0 +1,48 @@
+import {
+ START_FETCHING,
+ RECEIVE,
+ RECEIVE_ERROR,
+ BooksState,
+ BooksActionType
+} from './types';
+
+const initialState = {
+ items: [],
+ isFetching: false,
+ isFetched: false,
+ error: null
+};
+
+export default function(
+ state = initialState,
+ action: BooksActionType
+): BooksState {
+ switch (action.type) {
+ case START_FETCHING:
+ return {
+ ...state,
+ isFetching: true,
+ isFetched: false
+ };
+ case RECEIVE: {
+ const { books } = action.data;
+
+ // get uuids of deleted books and that of a currently selected book
+ return {
+ ...state,
+ isFetching: false,
+ isFetched: true,
+ items: [...state.items, ...books]
+ };
+ }
+ case RECEIVE_ERROR:
+ return {
+ ...state,
+ isFetching: false,
+ isFetched: true,
+ error: action.data.error
+ };
+ default:
+ return state;
+ }
+}
diff --git a/browser/src/scripts/store/books/types.ts b/browser/src/scripts/store/books/types.ts
new file mode 100644
index 00000000..d37c290c
--- /dev/null
+++ b/browser/src/scripts/store/books/types.ts
@@ -0,0 +1,35 @@
+export type BookData = any;
+
+export interface BooksState {
+ items: BookData[];
+ isFetching: boolean;
+ isFetched: boolean;
+ error: string | null;
+}
+
+export const START_FETCHING = 'books/START_FETCHING';
+export const RECEIVE = 'books/RECEIVE';
+export const RECEIVE_ERROR = 'books/RECEIVE_ERROR';
+
+export interface StartFetchingAction {
+ type: typeof START_FETCHING;
+}
+
+export interface ReceiveAction {
+ type: typeof RECEIVE;
+ data: {
+ books: BookData[];
+ };
+}
+
+export interface ReceiveErrorAction {
+ type: typeof RECEIVE_ERROR;
+ data: {
+ error: string;
+ };
+}
+
+export type BooksActionType =
+ | StartFetchingAction
+ | ReceiveAction
+ | ReceiveErrorAction;
diff --git a/browser/src/scripts/store/composer/actions.ts b/browser/src/scripts/store/composer/actions.ts
new file mode 100644
index 00000000..ef085a0b
--- /dev/null
+++ b/browser/src/scripts/store/composer/actions.ts
@@ -0,0 +1,47 @@
+import {
+ UPDATE_CONTENT,
+ UPDATE_BOOK,
+ RESET,
+ RESET_BOOK,
+ UpdateContentAction,
+ UpdateBookAction,
+ ResetBookAction,
+ ResetAction
+} from './types';
+
+export function updateContent(content: string): UpdateContentAction {
+ return {
+ type: UPDATE_CONTENT,
+ data: { content }
+ };
+}
+
+export interface UpdateBookActionParam {
+ uuid: string;
+ label: string;
+}
+
+export function updateBook({
+ uuid,
+ label
+}: UpdateBookActionParam): UpdateBookAction {
+ return {
+ type: UPDATE_BOOK,
+ data: {
+ uuid,
+ label
+ }
+ };
+}
+
+export function resetBook(): ResetBookAction {
+ return {
+ type: RESET_BOOK
+ };
+}
+
+export function resetComposer(): ResetAction {
+ return {
+ type: RESET
+ };
+}
diff --git a/browser/src/scripts/store/composer/reducers.ts b/browser/src/scripts/store/composer/reducers.ts
new file mode 100644
index 00000000..3cffeb9c
--- /dev/null
+++ b/browser/src/scripts/store/composer/reducers.ts
@@ -0,0 +1,47 @@
+import {
+ UPDATE_CONTENT,
+ UPDATE_BOOK,
+ RESET,
+ RESET_BOOK,
+ ComposerActionType,
+ ComposerState
+} from './types';
+
+const initialState: ComposerState = {
+ content: '',
+ bookUUID: '',
+ bookLabel: ''
+};
+
+export default function(
+ state = initialState,
+ action: ComposerActionType
+): ComposerState {
+ switch (action.type) {
+ case UPDATE_CONTENT: {
+ return {
+ ...state,
+ content: action.data.content
+ };
+ }
+ case UPDATE_BOOK: {
+ return {
+ ...state,
+ bookUUID: action.data.uuid,
+ bookLabel: action.data.label
+ };
+ }
+ case RESET_BOOK: {
+ return {
+ ...state,
+ bookUUID: '',
+ bookLabel: ''
+ };
+ }
+ case RESET: {
+ return initialState;
+ }
+ default:
+ return state;
+ }
+}
diff --git a/browser/src/scripts/store/composer/types.ts b/browser/src/scripts/store/composer/types.ts
new file mode 100644
index 00000000..5cea0ed1
--- /dev/null
+++ b/browser/src/scripts/store/composer/types.ts
@@ -0,0 +1,39 @@
+export interface ComposerState {
+ content: string;
+ bookUUID: string;
+ bookLabel: string;
+}
+
+export const UPDATE_CONTENT = 'composer/UPDATE_CONTENT';
+export const UPDATE_BOOK = 'composer/UPDATE_BOOK';
+export const RESET = 'composer/RESET';
+export const RESET_BOOK = 'composer/RESET_BOOK';
+
+export interface UpdateContentAction {
+ type: typeof UPDATE_CONTENT;
+ data: {
+ content: string;
+ };
+}
+
+export interface UpdateBookAction {
+ type: typeof UPDATE_BOOK;
+ data: {
+ uuid: string;
+ label: string;
+ };
+}
+
+export interface ResetAction {
+ type: typeof RESET;
+}
+
+export interface ResetBookAction {
+ type: typeof RESET_BOOK;
+}
+
+export type ComposerActionType =
+ | UpdateContentAction
+ | UpdateBookAction
+ | ResetAction
+ | ResetBookAction;
diff --git a/browser/src/scripts/store/hooks.ts b/browser/src/scripts/store/hooks.ts
new file mode 100644
index 00000000..0dacbdc6
--- /dev/null
+++ b/browser/src/scripts/store/hooks.ts
@@ -0,0 +1,36 @@
+import { Store, Action } from 'redux';
+import {
+ useDispatch as useReduxDispatch,
+ useStore as useReduxStore,
+ useSelector as useReduxSelector
+} from 'react-redux';
+import { ThunkDispatch } from 'redux-thunk';
+
+import { ComposerState } from './composer/types';
+import { LocationState } from './location/types';
+import { SettingsState } from './settings/types';
+import { BooksState } from './books/types';
+
+// AppState represents the application state
+interface AppState {
+ composer: ComposerState;
+ location: LocationState;
+ settings: SettingsState;
+ books: BooksState;
+}
+
+type ReduxDispatch = ThunkDispatch;
+
+export function useDispatch(): ReduxDispatch {
+ return useReduxDispatch();
+}
+
+export function useStore(): Store {
+ return useReduxStore();
+}
+
+export function useSelector(
+ selector: (state: AppState) => TSelected
+) {
+ return useReduxSelector(selector);
+}
diff --git a/browser/src/scripts/store/index.ts b/browser/src/scripts/store/index.ts
new file mode 100644
index 00000000..40836330
--- /dev/null
+++ b/browser/src/scripts/store/index.ts
@@ -0,0 +1,29 @@
+import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
+import thunkMiddleware from 'redux-thunk';
+import createLogger from 'redux-logger';
+
+import location from './location/reducers';
+import settings from './settings/reducers';
+import books from './books/reducers';
+import composer from './composer/reducers';
+
+const rootReducer = combineReducers({
+ location,
+ settings,
+ books,
+ composer
+});
+
+// configuruStore returns a new store that contains the appliation state
+export default function configureStore(initialState) {
+ const typedWindow = window as any;
+
+ const composeEnhancers =
+ typedWindow.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
+
+ return createStore(
+ rootReducer,
+ initialState,
+ composeEnhancers(applyMiddleware(createLogger, thunkMiddleware))
+ );
+}
diff --git a/browser/src/scripts/store/location/actions.ts b/browser/src/scripts/store/location/actions.ts
new file mode 100644
index 00000000..4ca15aec
--- /dev/null
+++ b/browser/src/scripts/store/location/actions.ts
@@ -0,0 +1,8 @@
+import { NAVIGATE, NavigateAction } from './types';
+
+export function navigate(path: string, state?): NavigateAction {
+ return {
+ type: NAVIGATE,
+ data: { path, state }
+ };
+}
diff --git a/browser/src/scripts/store/location/reducers.ts b/browser/src/scripts/store/location/reducers.ts
new file mode 100644
index 00000000..71ca7006
--- /dev/null
+++ b/browser/src/scripts/store/location/reducers.ts
@@ -0,0 +1,22 @@
+import { NAVIGATE, LocationState, LocationActionType } from './types';
+
+const initialState: LocationState = {
+ path: '/',
+ state: {}
+};
+
+export default function(
+ state = initialState,
+ action: LocationActionType
+): LocationState {
+ switch (action.type) {
+ case NAVIGATE:
+ return {
+ ...state,
+ path: action.data.path,
+ state: action.data.state || {}
+ };
+ default:
+ return state;
+ }
+}
diff --git a/browser/src/scripts/store/location/types.ts b/browser/src/scripts/store/location/types.ts
new file mode 100644
index 00000000..c9898071
--- /dev/null
+++ b/browser/src/scripts/store/location/types.ts
@@ -0,0 +1,16 @@
+export interface LocationState {
+ path: string;
+ state: any;
+}
+
+export const NAVIGATE = 'location/NAVIGATE';
+
+export interface NavigateAction {
+ type: typeof NAVIGATE;
+ data: {
+ path: string;
+ state: string;
+ };
+}
+
+export type LocationActionType = NavigateAction;
diff --git a/browser/src/scripts/store/settings/actions.ts b/browser/src/scripts/store/settings/actions.ts
new file mode 100644
index 00000000..b1a717b5
--- /dev/null
+++ b/browser/src/scripts/store/settings/actions.ts
@@ -0,0 +1,14 @@
+import { UPDATE, RESET, UpdateAction, ResetAction } from './types';
+
+export function updateSettings(settings): UpdateAction {
+ return {
+ type: UPDATE,
+ data: { settings }
+ };
+}
+
+export function resetSettings(): ResetAction {
+ return {
+ type: RESET
+ };
+}
diff --git a/browser/src/scripts/store/settings/reducers.ts b/browser/src/scripts/store/settings/reducers.ts
new file mode 100644
index 00000000..87fd1e18
--- /dev/null
+++ b/browser/src/scripts/store/settings/reducers.ts
@@ -0,0 +1,23 @@
+import { UPDATE, RESET, SettingsState, SettingsActionType } from './types';
+
+const initialState: SettingsState = {
+ sessionKey: '',
+ sessionKeyExpiry: 0
+};
+
+export default function(
+ state = initialState,
+ action: SettingsActionType
+): SettingsState {
+ switch (action.type) {
+ case UPDATE:
+ return {
+ ...state,
+ ...action.data.settings
+ };
+ case RESET:
+ return initialState;
+ default:
+ return state;
+ }
+}
diff --git a/browser/src/scripts/store/settings/types.ts b/browser/src/scripts/store/settings/types.ts
new file mode 100644
index 00000000..993fc00e
--- /dev/null
+++ b/browser/src/scripts/store/settings/types.ts
@@ -0,0 +1,20 @@
+export interface SettingsState {
+ sessionKey: string;
+ sessionKeyExpiry: number;
+}
+
+export const UPDATE = 'settings/UPDATE';
+export const RESET = 'settings/RESET';
+
+export interface UpdateAction {
+ type: typeof UPDATE;
+ data: {
+ settings: any;
+ };
+}
+
+export interface ResetAction {
+ type: typeof RESET;
+}
+
+export type SettingsActionType = UpdateAction | ResetAction;
diff --git a/browser/src/scripts/utils/config.ts b/browser/src/scripts/utils/config.ts
new file mode 100644
index 00000000..b773d81d
--- /dev/null
+++ b/browser/src/scripts/utils/config.ts
@@ -0,0 +1,5 @@
+export default {
+ webUrl: __WEB_URL__,
+ apiEndpoint: __API_ENDPOINT__,
+ version: __VERSION__
+};
diff --git a/browser/src/scripts/utils/ext.ts b/browser/src/scripts/utils/ext.ts
new file mode 100644
index 00000000..db70e24f
--- /dev/null
+++ b/browser/src/scripts/utils/ext.ts
@@ -0,0 +1,37 @@
+// module ext provides a cross-browser interface to access extension APIs
+// by using WebExtensions API if available, and using Chrome as a fallback.
+let ext: any = {};
+
+const apis = ['tabs', 'storage', 'runtime'];
+
+for (let i = 0; i < apis.length; i++) {
+ const api = apis[i];
+
+ try {
+ if (browser[api]) {
+ ext[api] = browser[api];
+ }
+ } catch (e) {}
+
+ try {
+ if (chrome[api] && !ext[api]) {
+ ext[api] = chrome[api];
+
+ // Standardize the signature to conform to WebExtensions API
+ if (api === 'tabs') {
+ const fn = ext[api].create;
+
+ // Promisify chrome.tabs.create
+ ext[api].create = function(obj) {
+ return new Promise(resolve => {
+ fn(obj, function(tab) {
+ resolve(tab);
+ });
+ });
+ };
+ }
+ }
+ } catch (e) {}
+}
+
+export default ext;
diff --git a/browser/src/scripts/utils/fetch.js b/browser/src/scripts/utils/fetch.js
new file mode 100644
index 00000000..ec50090a
--- /dev/null
+++ b/browser/src/scripts/utils/fetch.js
@@ -0,0 +1,48 @@
+import qs from 'qs';
+
+function checkStatus(response) {
+ if (response.status >= 200 && response.status < 300) {
+ return response;
+ }
+ return response.text().then((body) => {
+ const error = new Error(body);
+ error.response = response;
+
+ throw error;
+ });
+}
+
+function parseJSON(response) {
+ if (response.headers.get('Content-Type') === 'application/json') {
+ return response.json();
+ }
+
+ return Promise.resolve();
+}
+
+function request(url, options) {
+ return fetch(url, options)
+ .then(checkStatus)
+ .then(parseJSON);
+}
+
+export function post(url, data, options = {}) {
+ return request(url, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ ...options,
+ });
+}
+
+export function get(url, options = {}) {
+ let endpoint = url;
+
+ if (options.params) {
+ endpoint = `${endpoint}?${qs.stringify(options.params)}`;
+ }
+
+ return request(endpoint, {
+ method: 'GET',
+ ...options,
+ });
+}
diff --git a/browser/src/scripts/utils/services.ts b/browser/src/scripts/utils/services.ts
new file mode 100644
index 00000000..05053daa
--- /dev/null
+++ b/browser/src/scripts/utils/services.ts
@@ -0,0 +1,9 @@
+import initServices from 'jslib/services';
+import config from './config';
+
+const services = initServices({
+ baseUrl: config.apiEndpoint,
+ pathPrefix: ''
+});
+
+export default services;
diff --git a/browser/src/scripts/utils/storage.ts b/browser/src/scripts/utils/storage.ts
new file mode 100644
index 00000000..733b5726
--- /dev/null
+++ b/browser/src/scripts/utils/storage.ts
@@ -0,0 +1,51 @@
+import ext from "./ext";
+
+const stateKey = "state";
+
+// filterState filters the given state to be suitable for reuse upon next app
+// load
+function filterState(state) {
+ return {
+ ...state,
+ location: {
+ ...state.location,
+ path: "/"
+ },
+ books: {
+ ...state.books,
+ items: state.books.items.filter(item => {
+ return !item.isNew || item.selected;
+ })
+ }
+ };
+}
+
+function parseStorageItem(item) {
+ if (!item) {
+ return null;
+ }
+
+ return JSON.parse(item);
+}
+
+// saveState writes the given state to storage
+export function saveState(state) {
+ const filtered = filterState(state);
+ const serialized = JSON.stringify(filtered);
+
+ ext.storage.local.set({ [stateKey]: serialized }, () => {
+ console.log("synced state");
+ });
+}
+
+// loadState loads and parses serialized state stored in ext.storage
+export function loadState(done) {
+ ext.storage.local.get("state", items => {
+ const parsed = {
+ ...items,
+ state: parseStorageItem(items.state)
+ };
+
+ return done(parsed);
+ });
+}
diff --git a/browser/src/styles/popup.css b/browser/src/styles/popup.css
new file mode 100644
index 00000000..1608a48b
--- /dev/null
+++ b/browser/src/styles/popup.css
@@ -0,0 +1,375 @@
+/* container width: 345px */
+
+html {
+ font-size: 62.5%;
+ /* 1.0 rem = 10px */
+}
+body {
+ margin: 0;
+ font-family: 'Lato', sans-serif;
+ font-size: 1.6rem;
+}
+body.pending {
+ height: 257px;
+ width: 345px;
+}
+main.blur {
+ opacity: 0.15;
+}
+.container {
+ width: 345px;
+ position: relative;
+}
+.input {
+ display: block;
+ width: 100%;
+ padding: .375rem .75rem;
+ font-size: 1rem;
+ line-height: 1.5;
+ color: #495057;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid #ced4da;
+ border-radius: .25rem;
+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
+ box-sizing: border-box;
+ font-size: 12px;
+}
+.login-input {
+ margin-top: 4px;
+}
+.message {
+ color: #2cae2c;
+ margin-bottom: 6px;
+}
+.alert.error {
+ padding: 10px 9px;
+ font-size: 1.4rem;
+ color: #721c24;
+ background-color: #f8d7da;
+ border-color: #f5c6cb;
+}
+kbd {
+ display: inline-block;
+ padding: 3px 5px;
+ font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ line-height: 10px;
+ color: #444d56;
+ vertical-align: middle;
+ background-color: #fafbfc;
+ border: solid 1px #d1d5da;
+ border-bottom-color: #c6cbd1;
+ border-radius: 3px;
+ box-shadow: inset 0 -1px 0 #c6cbd1;
+}
+.menu {
+ position: absolute;
+ left: 0;
+ right: 0;
+ background: white;
+ margin-top: 0;
+ margin-bottom: 0;
+ z-index: 1;
+ list-style: none;
+ padding-left: 0;
+ margin-bottom: 0;
+ box-shadow: 0px 3px 2px 0px #cacaca;
+}
+.menu .logout-button {
+ width: 100%;
+ border: none;
+ background: none;
+ text-align: left;
+ cursor: pointer;
+}
+.menu-link {
+ display: block;
+ padding: 10px 13px;
+ color: inherit;
+ text-decoration: none;
+ font-size: 16px;
+}
+.menu-link:hover {
+ background: #f7f7f7;
+}
+.menu-link:not(:hover) {
+ background: inherit;
+}
+.menu-overlay {
+ position: absolute;
+ bottom: 0;
+ top: 44px;
+ left: 0;
+ right: 0;
+ opacity: 0.8;
+ background: #f7f7f7;
+}
+.header {
+ background-color: #272a35;
+ height: 44px;
+ display: flex;
+ align-items: center;
+ padding: 0 8px;
+ justify-content: space-between;
+}
+.header .logo {
+ width: 32px;
+}
+.header .logo-link {
+ height: 32px;
+}
+.header .menu-toggle {
+ height: 20px;
+}
+.home {
+ text-align: center;
+ padding: 16px 28px;
+}
+.home .greet {
+ font-size: 2.2rem;
+ margin-top: 0;
+}
+.home .lead {
+ color: #575757;
+}
+.home #login-form {
+ text-align: left;
+}
+.home #login-form label {
+ display: inline-block;
+ font-weight: 600;
+ margin-top: 8px;
+}
+.home .login-btn {
+ width: 100%;
+ justify-content: center;
+ margin-top: 12px;
+}
+.home .actions {
+ margin-top: 18px;
+ font-size: 1.3rem;
+ color: gray;
+}
+.home .actions a {
+ color: gray;
+ font-weight: 600;
+}
+.home .actions .signup:visited {
+ color: inherit;
+}
+.settings {
+ padding: 15px 8px;
+}
+.settings .label {
+ font-size: 1.4rem;
+ margin-bottom: 6px;
+ display: inline-block;
+}
+.settings .actions {
+ margin-top: 12px;
+}
+.settings .hint {
+ font-size: 1.4rem;
+ color: #7e7e7e;
+ margin-top: 4px;
+}
+.composer .form {
+ display: flex;
+ flex-direction: column;
+}
+.composer .content-container {
+ position: relative;
+ height: 148px;
+}
+.composer .content {
+ border: none;
+ resize: none;
+ height: 100%;
+ width: 100%;
+ padding: 11px 11px 18px 11px;
+ font-size: 1.5rem;
+ margin-bottom: 0;
+ box-sizing: border-box;
+ border: 1px solid #e2e2e2;
+ border-top: 0;
+}
+.composer .content::placeholder {
+ color: #aaa;
+}
+.composer .content:focus {
+ box-shadow: inset 0px 0px 3px #c0c0c0;
+ outline: none;
+}
+.composer .shortcut-hint {
+ position: absolute;
+ bottom: 3px;
+ right: 7px;
+ color: gray;
+ font-size: 1.2rem;
+ font-style: italic;
+}
+.composer .shortcut-hint:not(.shown) {
+ visibility: hidden;
+}
+.composer .submit-button {
+ color: #fff;
+ background-color: #272a35;
+ border-radius: 0;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 400;
+ line-height: 1.25;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ user-select: none;
+ transition: all 0.2s ease-in-out;
+ font-size: 1.4rem;
+ text-decoration: none;
+ padding: 11px 18px;
+ border-radius: 5px;
+ cursor: pointer;
+ box-sizing: border-box;
+ padding: 6px 0;
+ border-radius: 0;
+ border: none;
+}
+.composer .submit-button:hover {
+ color: #fff;
+ background-color: #323642;
+ box-shadow: 0px 0px 4px 2px #cacaca;
+}
+.composer .book-value {
+ display: flex;
+ align-items: center;
+}
+.composer .book-value .book-icon {
+ margin-right: 9px;
+ margin-top: 1px;
+}
+.composer .book-option {
+ font-size: 1.4rem;
+ padding: 5px 8px;
+}
+
+.success-page {
+ text-align: center;
+ padding: 21px 0;
+}
+.success-page .key-list {
+ display: inline-block;
+ list-style: none;
+ padding-left: 0;
+ margin-bottom: 0;
+}
+.success-page .key-item {
+ display: flex;
+}
+.success-page .key-item:not(:first-child) {
+ margin-top: 8px;
+}
+.success-page .key-desc {
+ font-size: 1.4rem;
+ margin-left: 9px;
+}
+.success-page .book-icon {
+ vertical-align: middle;
+}
+.success-page .heading {
+ display: inline-block;
+ font-size: 2.2rem;
+ margin-left: 13px;
+ margin-top: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+/* buttons */
+.button {
+ display: inline-flex;
+ align-items: center;
+ font-weight: 400;
+ line-height: 1.25;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ user-select: none;
+ border-width: 2px;
+ border-style: solid;
+ border-color: transparent;
+ border-image: initial;
+ padding: 0.5rem 1rem;
+ border-radius: 0.25rem;
+ transition: all 0.2s ease-in-out;
+ font-size: 1.4rem;
+ text-decoration: none;
+ padding: 11px 18px;
+ border-radius: 5px;
+ cursor: pointer;
+ box-sizing: border-box;
+}
+
+.button:hover {
+ text-decoration: none;
+}
+
+.button:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+.button:icon {
+ margin-right: 3px;
+}
+
+.button-first {
+ color: #ffffff;
+ background-color: #333745;
+}
+.button-first:hover {
+ color: #ffffff;
+ background-color: #252833;
+ box-shadow: 0px 0px 4px 2px #cacaca;
+}
+
+.button-first-outline {
+ background: transparent;
+ border-color: #333745;
+ color: #333744;
+}
+.button-first-outline:hover {
+ color: #333744;
+}
+
+.button-second {
+ background: white;
+ color: #343a40;
+}
+.button-second:hover {
+ color: #343a40;
+}
+
+.button-third {
+ color: #ffffff;
+ background-color: #4577cc;
+}
+.button-third:hover {
+ color: #ffffff;
+ background-color: #245fc5;
+ box-shadow: 0px 0px 4px 2px #cacaca;
+}
+
+.button-outline {
+ color: #0366d6;
+ background-color: #fff;
+ border: 2px solid #0366d6;
+}
+
+.button ~ .button {
+ margin-left: 10px;
+}
+
+.button-small {
+ padding: 5px 14px;
+}
diff --git a/browser/src/styles/select.css b/browser/src/styles/select.css
new file mode 100644
index 00000000..399ea17e
--- /dev/null
+++ b/browser/src/styles/select.css
@@ -0,0 +1,435 @@
+/**
+ * React Select
+ * ============
+ * Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/
+ * https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs
+ * MIT License: https://github.com/JedWatson/react-select
+*/
+.Select {
+ position: relative;
+}
+.Select input::-webkit-contacts-auto-fill-button,
+.Select input::-webkit-credentials-auto-fill-button {
+ display: none !important;
+}
+.Select input::-ms-clear {
+ display: none !important;
+}
+.Select input::-ms-reveal {
+ display: none !important;
+}
+.Select,
+.Select div,
+.Select input,
+.Select span {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.Select.is-disabled .Select-arrow-zone {
+ cursor: default;
+ pointer-events: none;
+ opacity: 0.35;
+}
+.Select.is-disabled > .Select-control {
+ background-color: #f9f9f9;
+}
+.Select.is-disabled > .Select-control:hover {
+ box-shadow: none;
+}
+.Select.is-open > .Select-control {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ background: #fff;
+ border-color: #b3b3b3 #ccc #d9d9d9;
+}
+.Select.is-open > .Select-control .Select-arrow {
+ top: -2px;
+ border-color: transparent transparent #999;
+ border-width: 0 5px 5px;
+}
+.Select.is-searchable.is-open > .Select-control {
+ cursor: text;
+}
+.Select.is-searchable.is-focused:not(.is-open) > .Select-control {
+ cursor: text;
+}
+.Select.is-focused > .Select-control {
+ background: #fff;
+}
+.Select.is-focused:not(.is-open) > .Select-control {
+ border-color: #007eff;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+ 0 0 0 3px rgba(0, 126, 255, 0.1);
+ background: #fff;
+}
+.Select.has-value.is-clearable.Select--single > .Select-control .Select-value {
+ padding-right: 42px;
+}
+.Select.has-value.Select--single
+ > .Select-control .Select-value .Select-value-label,
+.Select.has-value.is-pseudo-focused.Select--single
+ > .Select-control .Select-value .Select-value-label {
+ color: #333;
+}
+.Select.has-value.Select--single
+ > .Select-control .Select-value a.Select-value-label,
+.Select.has-value.is-pseudo-focused.Select--single
+ > .Select-control .Select-value a.Select-value-label {
+ cursor: pointer;
+ text-decoration: none;
+}
+.Select.has-value.Select--single
+ > .Select-control .Select-value a.Select-value-label:hover,
+.Select.has-value.is-pseudo-focused.Select--single
+ > .Select-control .Select-value a.Select-value-label:hover,
+.Select.has-value.Select--single
+ > .Select-control .Select-value a.Select-value-label:focus,
+.Select.has-value.is-pseudo-focused.Select--single
+ > .Select-control .Select-value a.Select-value-label:focus {
+ color: #007eff;
+ outline: none;
+ text-decoration: underline;
+}
+.Select.has-value.Select--single
+ > .Select-control .Select-value a.Select-value-label:focus,
+.Select.has-value.is-pseudo-focused.Select--single
+ > .Select-control .Select-value a.Select-value-label:focus {
+ background: #fff;
+}
+.Select.has-value.is-pseudo-focused .Select-input {
+ opacity: 0;
+}
+.Select.is-open .Select-arrow,
+.Select .Select-arrow-zone:hover > .Select-arrow {
+ border-top-color: #666;
+}
+.Select.Select--rtl {
+ direction: rtl;
+ text-align: right;
+}
+.Select-control {
+ background-color: #fff;
+ color: #333;
+ cursor: default;
+ display: table;
+ border: 1px solid #e2e2e2;
+ border-top: 0;
+ height: 36px;
+ outline: none;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+}
+.Select-control:hover {
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
+}
+.Select-control .Select-input:focus {
+ outline: none;
+ background: #fff;
+}
+.Select-placeholder,
+.Select--single > .Select-control .Select-value {
+ bottom: 0;
+ color: #aaa;
+ left: 0;
+ line-height: 34px;
+ padding-left: 10px;
+ padding-right: 10px;
+ position: absolute;
+ right: 0;
+ top: 0;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.Select-input {
+ height: 34px;
+ padding-left: 10px;
+ padding-right: 10px;
+ vertical-align: middle;
+}
+.Select-input > input {
+ width: 100%;
+ background: none transparent;
+ border: 0 none;
+ box-shadow: none;
+ cursor: default;
+ display: inline-block;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ outline: none;
+ line-height: 17px;
+ /* For IE 8 compatibility */
+ padding: 8px 0 12px;
+ /* For IE 8 compatibility */
+ -webkit-appearance: none;
+}
+.is-focused .Select-input > input {
+ cursor: text;
+}
+.has-value.is-pseudo-focused .Select-input {
+ opacity: 0;
+}
+.Select-control:not(.is-searchable) > .Select-input {
+ outline: none;
+}
+.Select-loading-zone {
+ cursor: pointer;
+ display: table-cell;
+ position: relative;
+ text-align: center;
+ vertical-align: middle;
+ width: 16px;
+}
+.Select-loading {
+ -webkit-animation: Select-animation-spin 400ms infinite linear;
+ -o-animation: Select-animation-spin 400ms infinite linear;
+ animation: Select-animation-spin 400ms infinite linear;
+ width: 16px;
+ height: 16px;
+ box-sizing: border-box;
+ border-radius: 50%;
+ border: 2px solid #ccc;
+ border-right-color: #333;
+ display: inline-block;
+ position: relative;
+ vertical-align: middle;
+}
+.Select-clear-zone {
+ -webkit-animation: Select-animation-fadeIn 200ms;
+ -o-animation: Select-animation-fadeIn 200ms;
+ animation: Select-animation-fadeIn 200ms;
+ color: #999;
+ cursor: pointer;
+ display: table-cell;
+ position: relative;
+ text-align: center;
+ vertical-align: middle;
+ width: 17px;
+}
+.Select-clear-zone:hover {
+ color: #d0021b;
+}
+.Select-clear {
+ display: inline-block;
+ font-size: 18px;
+ line-height: 1;
+}
+.Select--multi .Select-clear-zone {
+ width: 17px;
+}
+.Select-arrow-zone {
+ cursor: pointer;
+ display: table-cell;
+ position: relative;
+ text-align: center;
+ vertical-align: middle;
+ width: 25px;
+ padding-right: 5px;
+}
+.Select--rtl .Select-arrow-zone {
+ padding-right: 0;
+ padding-left: 5px;
+}
+.Select-arrow {
+ border-color: #999 transparent transparent;
+ border-style: solid;
+ border-width: 5px 5px 2.5px;
+ display: inline-block;
+ height: 0;
+ width: 0;
+ position: relative;
+}
+.Select-control > *:last-child {
+ padding-right: 5px;
+}
+.Select--multi .Select-multi-value-wrapper {
+ display: inline-block;
+}
+.Select .Select-aria-only {
+ position: absolute;
+ display: inline-block;
+ height: 1px;
+ width: 1px;
+ margin: -1px;
+ clip: rect(0, 0, 0, 0);
+ overflow: hidden;
+ float: left;
+}
+@-webkit-keyframes Select-animation-fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+@keyframes Select-animation-fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+.Select-menu-outer {
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border-top-color: #e6e6e6;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
+ box-sizing: border-box;
+ margin-top: -1px;
+ max-height: 200px;
+ position: absolute;
+ left: 0;
+ top: 100%;
+ width: 100%;
+ z-index: 1;
+ -webkit-overflow-scrolling: touch;
+}
+.Select-menu {
+ max-height: 150px;
+ overflow-y: auto;
+}
+.Select-option {
+ box-sizing: border-box;
+ background-color: #fff;
+ color: #666666;
+ cursor: pointer;
+ display: block;
+ padding: 8px 10px;
+}
+.Select-option:last-child {
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+.Select-option.is-selected {
+ background-color: #f5faff;
+ /* Fallback color for IE 8 */
+ background-color: rgba(0, 126, 255, 0.04);
+ color: #333;
+}
+.Select-option.is-focused {
+ background-color: #ebf5ff;
+ /* Fallback color for IE 8 */
+ background-color: rgba(0, 126, 255, 0.08);
+ color: #333;
+}
+.Select-option.is-disabled {
+ color: #cccccc;
+ cursor: default;
+}
+.Select-noresults {
+ box-sizing: border-box;
+ color: #999999;
+ cursor: default;
+ display: block;
+ padding: 8px 10px;
+}
+.Select--multi .Select-input {
+ vertical-align: middle;
+ margin-left: 10px;
+ padding: 0;
+}
+.Select--multi.Select--rtl .Select-input {
+ margin-left: 0;
+ margin-right: 10px;
+}
+.Select--multi.has-value .Select-input {
+ margin-left: 5px;
+}
+.Select--multi .Select-value {
+ background-color: #ebf5ff;
+ /* Fallback color for IE 8 */
+ background-color: rgba(0, 126, 255, 0.08);
+ border-radius: 2px;
+ border: 1px solid #c2e0ff;
+ /* Fallback color for IE 8 */
+ border: 1px solid rgba(0, 126, 255, 0.24);
+ color: #007eff;
+ display: inline-block;
+ font-size: 0.9em;
+ line-height: 1.4;
+ margin-left: 5px;
+ margin-top: 5px;
+ vertical-align: top;
+}
+.Select--multi .Select-value-icon,
+.Select--multi .Select-value-label {
+ display: inline-block;
+ vertical-align: middle;
+}
+.Select--multi .Select-value-label {
+ border-bottom-right-radius: 2px;
+ border-top-right-radius: 2px;
+ cursor: default;
+ padding: 2px 5px;
+}
+.Select--multi a.Select-value-label {
+ color: #007eff;
+ cursor: pointer;
+ text-decoration: none;
+}
+.Select--multi a.Select-value-label:hover {
+ text-decoration: underline;
+}
+.Select--multi .Select-value-icon {
+ cursor: pointer;
+ border-bottom-left-radius: 2px;
+ border-top-left-radius: 2px;
+ border-right: 1px solid #c2e0ff;
+ /* Fallback color for IE 8 */
+ border-right: 1px solid rgba(0, 126, 255, 0.24);
+ padding: 1px 5px 3px;
+}
+.Select--multi .Select-value-icon:hover,
+.Select--multi .Select-value-icon:focus {
+ background-color: #d8eafd;
+ /* Fallback color for IE 8 */
+ background-color: rgba(0, 113, 230, 0.08);
+ color: #0071e6;
+}
+.Select--multi .Select-value-icon:active {
+ background-color: #c2e0ff;
+ /* Fallback color for IE 8 */
+ background-color: rgba(0, 126, 255, 0.24);
+}
+.Select--multi.Select--rtl .Select-value {
+ margin-left: 0;
+ margin-right: 5px;
+}
+.Select--multi.Select--rtl .Select-value-icon {
+ border-right: none;
+ border-left: 1px solid #c2e0ff;
+ /* Fallback color for IE 8 */
+ border-left: 1px solid rgba(0, 126, 255, 0.24);
+}
+.Select--multi.is-disabled .Select-value {
+ background-color: #fcfcfc;
+ border: 1px solid #e3e3e3;
+ color: #333;
+}
+.Select--multi.is-disabled .Select-value-icon {
+ cursor: not-allowed;
+ border-right: 1px solid #e3e3e3;
+}
+.Select--multi.is-disabled .Select-value-icon:hover,
+.Select--multi.is-disabled .Select-value-icon:focus,
+.Select--multi.is-disabled .Select-value-icon:active {
+ background-color: #fcfcfc;
+}
+@keyframes Select-animation-spin {
+ to {
+ transform: rotate(1turn);
+ }
+}
+@-webkit-keyframes Select-animation-spin {
+ to {
+ -webkit-transform: rotate(1turn);
+ }
+}
diff --git a/browser/tsconfig.json b/browser/tsconfig.json
new file mode 100644
index 00000000..f3e56856
--- /dev/null
+++ b/browser/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "sourceMap": true,
+ "esModuleInterop": true,
+ "noImplicitAny": false,
+ "module": "es6",
+ "moduleResolution": "node",
+ "jsx": "react",
+ "allowJs": true,
+ "target": "es5",
+ "baseUrl": ".",
+ "paths": {
+ "jslib/*": [
+ "../jslib/src/*"
+ ]
+ }
+ }
+}
diff --git a/browser/webpack.config.js b/browser/webpack.config.js
new file mode 100644
index 00000000..3776de97
--- /dev/null
+++ b/browser/webpack.config.js
@@ -0,0 +1,58 @@
+const path = require('path');
+const webpack = require('webpack');
+const packageJson = require('./package.json');
+
+const ENV = process.env.NODE_ENV;
+const TARGET = process.env.TARGET;
+const isProduction = ENV === 'production';
+
+console.log(`Running webpack in ${ENV} mode`);
+
+const webUrl = isProduction
+ ? 'https://app.getdnote.com'
+ : 'http://127.0.0.1:3000';
+const apiUrl = isProduction
+ ? 'https://api.getdnote.com'
+ : 'http://127.0.0.1:5000';
+
+const plugins = [
+ new webpack.DefinePlugin({
+ __API_ENDPOINT__: JSON.stringify(apiUrl),
+ __WEB_URL__: JSON.stringify(webUrl),
+ __VERSION__: JSON.stringify(packageJson.version)
+ })
+];
+
+const moduleRules = [
+ {
+ test: /\.ts(x?)$/,
+ exclude: /node_modules|_test\.ts(x)$/,
+ loaders: ['ts-loader'],
+ exclude: path.resolve(__dirname, 'node_modules')
+ }
+];
+
+module.exports = env => {
+ return {
+ // run in production mode because of Content Security Policy error encountered
+ // when running a JavaScript bundle produced in a development mode
+ mode: 'production',
+ entry: { popup: ['./src/scripts/popup.tsx'] },
+ output: {
+ filename: '[name].js',
+ path: path.resolve(__dirname, 'dist', TARGET, 'scripts')
+ },
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js'],
+ alias: {
+ jslib: path.join(__dirname, '../jslib/src')
+ },
+ modules: [path.resolve('node_modules')]
+ },
+ module: { rules: moduleRules },
+ plugins: plugins,
+ optimization: {
+ minimize: isProduction
+ }
+ };
+};
diff --git a/jslib/.gitignore b/jslib/.gitignore
new file mode 100644
index 00000000..521a8db6
--- /dev/null
+++ b/jslib/.gitignore
@@ -0,0 +1,3 @@
+/dist
+/node_modules
+/coverage
diff --git a/jslib/README.md b/jslib/README.md
new file mode 100644
index 00000000..5e0afb2f
--- /dev/null
+++ b/jslib/README.md
@@ -0,0 +1,3 @@
+# jslib
+
+Code shared between Dnote JavaScript projects.
diff --git a/jslib/karma.conf.js b/jslib/karma.conf.js
new file mode 100644
index 00000000..70483fe3
--- /dev/null
+++ b/jslib/karma.conf.js
@@ -0,0 +1,49 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+module.exports = config => {
+ config.set({
+ frameworks: ['mocha', 'karma-typescript'],
+ reporters: ['mocha', 'karma-typescript'],
+ browsers: ['ChromeHeadlessNoSandbox'],
+ customLaunchers: {
+ ChromeHeadlessNoSandbox: {
+ base: 'ChromeHeadless',
+ // specified because the default user of gitlab ci docker image is root, and chrome does not
+ // support running as root without no-sandbox
+ flags: ['--no-sandbox']
+ }
+ },
+ files: [
+ './src/**/*.ts'
+ ],
+ preprocessors: {
+ '**/*.ts': 'karma-typescript'
+ },
+ karmaTypescriptConfig: {
+ tsconfig: './tsconfig.json',
+ bundlerOptions: {
+ entrypoints: /\_test\.ts$/,
+ sourceMap: true
+ }
+ },
+ mochaReporter: {
+ showDiff: true
+ }
+ });
+};
diff --git a/jslib/package-lock.json b/jslib/package-lock.json
new file mode 100644
index 00000000..7080ba2c
--- /dev/null
+++ b/jslib/package-lock.json
@@ -0,0 +1,3896 @@
+{
+ "name": "jslib",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz",
+ "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "@types/history": {
+ "version": "4.7.3",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.3.tgz",
+ "integrity": "sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw=="
+ },
+ "@types/mocha": {
+ "version": "5.2.7",
+ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
+ "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
+ "dev": true
+ },
+ "abbrev": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
+ "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=",
+ "dev": true
+ },
+ "accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ }
+ },
+ "acorn": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
+ "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+ "dev": true
+ },
+ "acorn-walk": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
+ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==",
+ "dev": true
+ },
+ "after": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
+ "dev": true
+ },
+ "amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+ "dev": true,
+ "optional": true
+ },
+ "ansi-colors": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "ansi-wrap": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
+ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
+ "dev": true
+ },
+ "anymatch": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.0.tgz",
+ "integrity": "sha512-Ozz7l4ixzI7Oxj2+cw+p0tVUt27BpaJ+1+q1TCeANWxHpvyn2+Un+YamBdfKu0uh8xLodGhoa1v7595NhKDAuA==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-find-index": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+ "dev": true
+ },
+ "arraybuffer.slice": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
+ "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
+ "dev": true
+ },
+ "asn1.js": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "assert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz",
+ "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==",
+ "dev": true,
+ "requires": {
+ "es6-object-assign": "^1.1.0",
+ "is-nan": "^1.2.1",
+ "object-is": "^1.0.1",
+ "util": "^0.12.0"
+ }
+ },
+ "assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "async": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.14"
+ }
+ },
+ "async-limiter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
+ "dev": true
+ },
+ "backo2": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base64-arraybuffer": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+ "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
+ "dev": true
+ },
+ "base64-js": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+ "dev": true
+ },
+ "base64id": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
+ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
+ "dev": true
+ },
+ "better-assert": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
+ "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
+ "dev": true,
+ "requires": {
+ "callsite": "1.0.0"
+ }
+ },
+ "binary-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
+ "dev": true
+ },
+ "blob": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
+ "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
+ "dev": true
+ },
+ "bluebird": {
+ "version": "3.5.5",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
+ "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "dev": true
+ },
+ "body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.1.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "on-finished": "~2.3.0",
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+ "dev": true
+ }
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browser-resolve": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
+ "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
+ "dev": true,
+ "requires": {
+ "resolve": "1.1.7"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "^1.0.4",
+ "browserify-des": "^1.0.0",
+ "evp_bytestokey": "^1.0.0"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "des.js": "^1.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "randombytes": "^2.0.1"
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.1",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.2",
+ "elliptic": "^6.0.0",
+ "inherits": "^2.0.1",
+ "parse-asn1": "^5.0.0"
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
+ "buffer": {
+ "version": "5.4.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
+ "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4"
+ }
+ },
+ "buffer-alloc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+ "dev": true,
+ "requires": {
+ "buffer-alloc-unsafe": "^1.1.0",
+ "buffer-fill": "^1.0.0"
+ }
+ },
+ "buffer-alloc-unsafe": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+ "dev": true
+ },
+ "buffer-fill": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+ "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+ "dev": true
+ },
+ "callsite": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^2.0.0",
+ "map-obj": "^1.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+ "dev": true
+ }
+ }
+ },
+ "chai": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
+ "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
+ "dev": true,
+ "requires": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.2",
+ "deep-eql": "^3.0.1",
+ "get-func-name": "^2.0.0",
+ "pathval": "^1.1.0",
+ "type-detect": "^4.0.5"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+ "dev": true
+ },
+ "chokidar": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.1.1.tgz",
+ "integrity": "sha512-df4o16uZmMHzVQwECZRHwfguOt5ixpuQVaZHjYMvYisgKhE+JXwcj/Tcr3+3bu/XeOJQ9ycYmzu7Mv8XrGxJDQ==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^3.1.0",
+ "braces": "^3.0.2",
+ "fsevents": "^2.0.6",
+ "glob-parent": "^5.0.0",
+ "is-binary-path": "^2.1.0",
+ "is-glob": "^4.0.1",
+ "normalize-path": "^3.0.0",
+ "readdirp": "^3.1.1"
+ }
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
+ "dev": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "colors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+ "dev": true
+ },
+ "combine-source-map": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
+ "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
+ "dev": true,
+ "requires": {
+ "convert-source-map": "~1.1.0",
+ "inline-source-map": "~0.6.0",
+ "lodash.memoize": "~3.0.3",
+ "source-map": "~0.5.3"
+ },
+ "dependencies": {
+ "convert-source-map": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+ "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ }
+ }
+ },
+ "commander": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
+ "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
+ "dev": true,
+ "optional": true
+ },
+ "component-bind": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+ "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+ "dev": true
+ },
+ "component-inherit": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "connect": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
+ "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "finalhandler": "1.1.2",
+ "parseurl": "~1.3.3",
+ "utils-merge": "1.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+ "dev": true,
+ "requires": {
+ "date-now": "^0.1.4"
+ }
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
+ "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ }
+ }
+ },
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+ "dev": true
+ },
+ "core-js": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
+ "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==",
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "create-ecdh": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.0.0"
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "^1.0.0",
+ "browserify-sign": "^4.0.0",
+ "create-ecdh": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.0",
+ "diffie-hellman": "^5.0.0",
+ "inherits": "^2.0.1",
+ "pbkdf2": "^3.0.3",
+ "public-encrypt": "^4.0.0",
+ "randombytes": "^2.0.0",
+ "randomfill": "^1.0.3"
+ }
+ },
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+ "dev": true,
+ "requires": {
+ "array-find-index": "^1.0.1"
+ }
+ },
+ "custom-event": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
+ "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=",
+ "dev": true
+ },
+ "date-format": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
+ "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
+ "dev": true
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+ "dev": true
+ },
+ "dateformat": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
+ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=",
+ "dev": true,
+ "requires": {
+ "get-stdin": "^4.0.1",
+ "meow": "^3.3.0"
+ }
+ },
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "deep-eql": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+ "dev": true,
+ "requires": {
+ "type-detect": "^4.0.0"
+ }
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "defaults": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+ "dev": true,
+ "requires": {
+ "clone": "^1.0.2"
+ }
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "dev": true
+ },
+ "des.js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
+ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "di": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
+ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=",
+ "dev": true
+ },
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ }
+ },
+ "dom-serialize": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
+ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=",
+ "dev": true,
+ "requires": {
+ "custom-event": "~1.0.0",
+ "ent": "~2.2.0",
+ "extend": "^3.0.0",
+ "void-elements": "^2.0.0"
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
+ "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.4.0",
+ "brorand": "^1.0.1",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.0"
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+ "dev": true
+ },
+ "end-of-stream": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.2.tgz",
+ "integrity": "sha512-gUSUszrsxlDnUbUwEI9Oygyrk4ZEWtVaHQc+uZHphVeNxl+qeqMV/jDWoTkjN1RmGlZ5QWAP7o458p/JMlikQg==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "engine.io": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz",
+ "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.4",
+ "base64id": "1.0.0",
+ "cookie": "0.3.1",
+ "debug": "~3.1.0",
+ "engine.io-parser": "~2.1.0",
+ "ws": "~3.3.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "engine.io-client": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
+ "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
+ "dev": true,
+ "requires": {
+ "component-emitter": "1.2.1",
+ "component-inherit": "0.0.3",
+ "debug": "~3.1.0",
+ "engine.io-parser": "~2.1.1",
+ "has-cors": "1.1.0",
+ "indexof": "0.0.1",
+ "parseqs": "0.0.5",
+ "parseuri": "0.0.5",
+ "ws": "~3.3.1",
+ "xmlhttprequest-ssl": "~1.5.4",
+ "yeast": "0.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "engine.io-parser": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
+ "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
+ "dev": true,
+ "requires": {
+ "after": "0.8.2",
+ "arraybuffer.slice": "~0.0.7",
+ "base64-arraybuffer": "0.1.5",
+ "blob": "0.0.5",
+ "has-binary2": "~1.0.2"
+ }
+ },
+ "ent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
+ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
+ "dev": true
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.14.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz",
+ "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.0",
+ "is-callable": "^1.1.4",
+ "is-regex": "^1.0.4",
+ "object-inspect": "^1.6.0",
+ "object-keys": "^1.1.1",
+ "string.prototype.trimleft": "^2.0.0",
+ "string.prototype.trimright": "^2.0.0"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "es6-object-assign": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
+ "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=",
+ "dev": true
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "escodegen": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
+ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
+ "dev": true,
+ "requires": {
+ "esprima": "^2.7.1",
+ "estraverse": "^1.9.1",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1",
+ "source-map": "~0.2.0"
+ },
+ "dependencies": {
+ "esprima": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+ "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "estraverse": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+ "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "eventemitter3": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
+ "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
+ "dev": true
+ },
+ "events": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
+ "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
+ "dev": true
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ }
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "flat": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
+ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
+ "dev": true,
+ "requires": {
+ "is-buffer": "~2.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+ "dev": true
+ },
+ "follow-redirects": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz",
+ "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.0.0"
+ }
+ },
+ "fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz",
+ "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+ "dev": true
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+ "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
+ "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
+ "dev": true
+ },
+ "growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+ "dev": true
+ },
+ "handlebars": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.0.tgz",
+ "integrity": "sha512-7XlnO8yBXOdi7AzowjZssQr47Ctidqm7GbgARapOaqSN9HQhlClnOkR9HieGauIT3A8MBC6u9wPCXs97PCYpWg==",
+ "dev": true,
+ "requires": {
+ "neo-async": "^2.6.0",
+ "optimist": "^0.6.1",
+ "source-map": "^0.6.1",
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-binary2": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
+ "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
+ "dev": true,
+ "requires": {
+ "isarray": "2.0.1"
+ }
+ },
+ "has-cors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+ "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+ "dev": true
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "history": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
+ "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "loose-envify": "^1.2.0",
+ "resolve-pathname": "^3.0.0",
+ "tiny-invariant": "^1.0.2",
+ "tiny-warning": "^1.0.0",
+ "value-equal": "^1.0.1"
+ }
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "hosted-git-info": {
+ "version": "2.8.4",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz",
+ "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==",
+ "dev": true
+ },
+ "http-errors": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ }
+ }
+ },
+ "http-proxy": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
+ "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
+ "dev": true,
+ "requires": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ieee754": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+ "dev": true
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+ "dev": true,
+ "requires": {
+ "repeating": "^2.0.0"
+ }
+ },
+ "indexof": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "inline-source-map": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
+ "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
+ "dev": true,
+ "requires": {
+ "source-map": "~0.5.3"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ }
+ }
+ },
+ "invert-kv": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+ "dev": true
+ },
+ "is-arguments": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
+ "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
+ "dev": true
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
+ "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+ "dev": true
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+ "dev": true
+ },
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-finite": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "is-generator-function": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz",
+ "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-nan": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz",
+ "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.1"
+ }
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
+ "is-symbol": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.0"
+ }
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+ "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
+ "dev": true
+ },
+ "isbinaryfile": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz",
+ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==",
+ "dev": true,
+ "requires": {
+ "buffer-alloc": "^1.2.0"
+ }
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "istanbul": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz",
+ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1.0.x",
+ "async": "1.x",
+ "escodegen": "1.8.x",
+ "esprima": "2.7.x",
+ "glob": "^5.0.15",
+ "handlebars": "^4.0.1",
+ "js-yaml": "3.x",
+ "mkdirp": "0.5.x",
+ "nopt": "3.x",
+ "once": "1.x",
+ "resolve": "1.1.x",
+ "supports-color": "^3.1.0",
+ "which": "^1.1.1",
+ "wordwrap": "^1.0.0"
+ },
+ "dependencies": {
+ "async": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+ "dev": true
+ },
+ "esprima": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+ "dev": true
+ },
+ "glob": {
+ "version": "5.0.15",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+ "dev": true,
+ "requires": {
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "2 || 3",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+ "dev": true,
+ "requires": {
+ "has-flag": "^1.0.0"
+ }
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ }
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+ "dev": true
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "karma": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/karma/-/karma-4.3.0.tgz",
+ "integrity": "sha512-NSPViHOt+RW38oJklvYxQC4BSQsv737oQlr/r06pCM+slDOr4myuI1ivkRmp+3dVpJDfZt2DmaPJ2wkx+ZZuMQ==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.3.0",
+ "body-parser": "^1.16.1",
+ "braces": "^3.0.2",
+ "chokidar": "^3.0.0",
+ "colors": "^1.1.0",
+ "connect": "^3.6.0",
+ "core-js": "^3.1.3",
+ "di": "^0.0.1",
+ "dom-serialize": "^2.2.0",
+ "flatted": "^2.0.0",
+ "glob": "^7.1.1",
+ "graceful-fs": "^4.1.2",
+ "http-proxy": "^1.13.0",
+ "isbinaryfile": "^3.0.0",
+ "lodash": "^4.17.14",
+ "log4js": "^4.0.0",
+ "mime": "^2.3.1",
+ "minimatch": "^3.0.2",
+ "optimist": "^0.6.1",
+ "qjobs": "^1.1.4",
+ "range-parser": "^1.2.0",
+ "rimraf": "^2.6.0",
+ "safe-buffer": "^5.0.1",
+ "socket.io": "2.1.1",
+ "source-map": "^0.6.1",
+ "tmp": "0.0.33",
+ "useragent": "2.3.0"
+ }
+ },
+ "karma-chrome-launcher": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz",
+ "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==",
+ "dev": true,
+ "requires": {
+ "which": "^1.2.1"
+ }
+ },
+ "karma-coverage": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-1.1.2.tgz",
+ "integrity": "sha512-eQawj4Cl3z/CjxslYy9ariU4uDh7cCNFZHNWXWRpl0pNeblY/4wHR7M7boTYXWrn9bY0z2pZmr11eKje/S/hIw==",
+ "dev": true,
+ "requires": {
+ "dateformat": "^1.0.6",
+ "istanbul": "^0.4.0",
+ "lodash": "^4.17.0",
+ "minimatch": "^3.0.0",
+ "source-map": "^0.5.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ }
+ }
+ },
+ "karma-mocha": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz",
+ "integrity": "sha1-7qrH/8DiAetjxGdEDStpx883eL8=",
+ "dev": true,
+ "requires": {
+ "minimist": "1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "karma-mocha-reporter": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz",
+ "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.1.0",
+ "log-symbols": "^2.1.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "karma-typescript": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/karma-typescript/-/karma-typescript-4.1.1.tgz",
+ "integrity": "sha512-NiGt3Lh8pxKY6hSW4mBV7X45zfB+EA4ezVMNN/vnzLvN+du0UoEc8lTAhrD/DMrjKP3wDlpabku652svRyguXg==",
+ "dev": true,
+ "requires": {
+ "acorn": "^6.0.5",
+ "acorn-walk": "^6.1.1",
+ "assert": "^2.0.0",
+ "async": "^3.0.1",
+ "browser-resolve": "^1.11.3",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^5.2.1",
+ "combine-source-map": "^0.8.0",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "^1.0.0",
+ "convert-source-map": "^1.6.0",
+ "crypto-browserify": "^3.12.0",
+ "diff": "^4.0.1",
+ "domain-browser": "^1.2.0",
+ "events": "^3.0.0",
+ "glob": "^7.1.3",
+ "https-browserify": "^1.0.0",
+ "istanbul": "0.4.5",
+ "json-stringify-safe": "^5.0.1",
+ "karma-coverage": "^1.1.1",
+ "lodash": "^4.17.11",
+ "log4js": "^4.0.1",
+ "minimatch": "^3.0.4",
+ "os-browserify": "^0.3.0",
+ "pad": "^3.2.0",
+ "path-browserify": "^1.0.0",
+ "process": "^0.11.10",
+ "punycode": "^2.1.1",
+ "querystring-es3": "^0.2.1",
+ "readable-stream": "^3.1.1",
+ "remap-istanbul": "^0.13.0",
+ "source-map": "^0.7.3",
+ "stream-browserify": "^2.0.2",
+ "stream-http": "^3.0.0",
+ "string_decoder": "^1.2.0",
+ "timers-browserify": "^2.0.10",
+ "tmp": "^0.1.0",
+ "tty-browserify": "^0.0.1",
+ "url": "^0.11.0",
+ "util": "^0.12.0",
+ "vm-browserify": "1.1.0"
+ },
+ "dependencies": {
+ "async": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz",
+ "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==",
+ "dev": true
+ },
+ "diff": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
+ "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "dev": true
+ },
+ "tmp": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
+ "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+ "dev": true,
+ "requires": {
+ "rimraf": "^2.6.3"
+ }
+ }
+ }
+ },
+ "lcid": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^2.0.0"
+ }
+ },
+ "levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ }
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0",
+ "strip-bom": "^2.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ },
+ "lodash.memoize": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
+ "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
+ "dev": true
+ },
+ "log-symbols": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.1"
+ }
+ },
+ "log4js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz",
+ "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==",
+ "dev": true,
+ "requires": {
+ "date-format": "^2.0.0",
+ "debug": "^4.1.1",
+ "flatted": "^2.0.0",
+ "rfdc": "^1.1.4",
+ "streamroller": "^1.0.6"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ }
+ }
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+ "dev": true,
+ "requires": {
+ "currently-unhandled": "^0.4.1",
+ "signal-exit": "^3.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+ "dev": true,
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "map-age-cleaner": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+ "dev": true,
+ "requires": {
+ "p-defer": "^1.0.0"
+ }
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+ "dev": true
+ },
+ "md5.js": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+ "dev": true
+ },
+ "mem": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
+ "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
+ "dev": true,
+ "requires": {
+ "map-age-cleaner": "^0.1.1",
+ "mimic-fn": "^2.0.0",
+ "p-is-promise": "^2.0.0"
+ }
+ },
+ "meow": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+ "dev": true,
+ "requires": {
+ "camelcase-keys": "^2.0.0",
+ "decamelize": "^1.1.2",
+ "loud-rejection": "^1.0.0",
+ "map-obj": "^1.0.1",
+ "minimist": "^1.1.3",
+ "normalize-package-data": "^2.3.4",
+ "object-assign": "^4.0.1",
+ "read-pkg-up": "^1.0.1",
+ "redent": "^1.0.0",
+ "trim-newlines": "^1.0.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ }
+ },
+ "mime": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+ "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
+ "dev": true
+ },
+ "mime-db": {
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+ "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.40.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "mocha": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz",
+ "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "3.2.3",
+ "browser-stdout": "1.3.1",
+ "debug": "3.2.6",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "find-up": "3.0.0",
+ "glob": "7.1.3",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "3.13.1",
+ "log-symbols": "2.2.0",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.1",
+ "ms": "2.1.1",
+ "node-environment-flags": "1.0.5",
+ "object.assign": "4.1.0",
+ "strip-json-comments": "2.0.1",
+ "supports-color": "6.0.0",
+ "which": "1.3.1",
+ "wide-align": "1.1.3",
+ "yargs": "13.2.2",
+ "yargs-parser": "13.0.0",
+ "yargs-unparser": "1.5.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ },
+ "negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+ "dev": true
+ },
+ "neo-async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "node-environment-flags": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
+ "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
+ "dev": true,
+ "requires": {
+ "object.getownpropertydescriptors": "^2.0.3",
+ "semver": "^5.7.0"
+ }
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1"
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+ "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ }
+ }
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "object-component": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
+ "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
+ "dev": true
+ },
+ "object-inspect": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
+ "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==",
+ "dev": true
+ },
+ "object-is": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz",
+ "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=",
+ "dev": true
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "has-symbols": "^1.0.0",
+ "object-keys": "^1.0.11"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
+ "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.12.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.5.1"
+ }
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dev": true,
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optimist": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "dev": true,
+ "requires": {
+ "minimist": "~0.0.1",
+ "wordwrap": "~0.0.2"
+ }
+ },
+ "optionator": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "dev": true,
+ "requires": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.4",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "wordwrap": "~1.0.0"
+ },
+ "dependencies": {
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ }
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "os-locale": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+ "dev": true,
+ "requires": {
+ "execa": "^1.0.0",
+ "lcid": "^2.0.0",
+ "mem": "^4.0.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "p-defer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+ "dev": true
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true
+ },
+ "p-is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
+ "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "pad": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz",
+ "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==",
+ "dev": true,
+ "requires": {
+ "wcwidth": "^1.0.1"
+ }
+ },
+ "pako": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
+ "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==",
+ "dev": true
+ },
+ "parse-asn1": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
+ "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^4.0.0",
+ "browserify-aes": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.0",
+ "pbkdf2": "^3.0.3",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "parseqs": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+ "dev": true,
+ "requires": {
+ "better-assert": "~1.0.0"
+ }
+ },
+ "parseuri": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
+ "dev": true,
+ "requires": {
+ "better-assert": "~1.0.0"
+ }
+ },
+ "parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true
+ },
+ "path-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.0.tgz",
+ "integrity": "sha512-Hkavx/nY4/plImrZPHRk2CL9vpOymZLgEbMNX1U0bjcBL7QN9wODxyx0yaMZURSQaUtSEvDrfAvxa9oPb0at9g==",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pathval": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+ "dev": true
+ },
+ "pbkdf2": {
+ "version": "3.0.17",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+ "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+ "dev": true,
+ "requires": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "picomatch": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
+ "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
+ "dev": true
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "plugin-error": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
+ "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^1.0.1",
+ "arr-diff": "^4.0.0",
+ "arr-union": "^3.1.0",
+ "extend-shallow": "^3.0.2"
+ },
+ "dependencies": {
+ "ansi-colors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
+ "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
+ "dev": true,
+ "requires": {
+ "ansi-wrap": "^0.1.0"
+ }
+ }
+ }
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "prettier": {
+ "version": "1.18.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
+ "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "qjobs": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
+ "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz",
+ "integrity": "sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA=="
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true
+ },
+ "raw-body": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^1.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^1.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+ "dev": true,
+ "requires": {
+ "find-up": "^1.0.0",
+ "read-pkg": "^1.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true,
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true,
+ "requires": {
+ "pinkie-promise": "^2.0.0"
+ }
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
+ "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.2.tgz",
+ "integrity": "sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.0.4"
+ }
+ },
+ "redent": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+ "dev": true,
+ "requires": {
+ "indent-string": "^2.1.0",
+ "strip-indent": "^1.0.1"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.3",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+ "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
+ },
+ "remap-istanbul": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/remap-istanbul/-/remap-istanbul-0.13.0.tgz",
+ "integrity": "sha512-rS5ZpVAx3fGtKZkiBe1esXg5mKYbgW9iz8kkADFt3p6lo3NsBBUX1q6SwdhwUtYCGnr7nK6gRlbYK3i8R0jbRA==",
+ "dev": true,
+ "requires": {
+ "istanbul": "0.4.5",
+ "minimatch": "^3.0.4",
+ "plugin-error": "^1.0.1",
+ "source-map": "0.6.1",
+ "through2": "3.0.0"
+ }
+ },
+ "repeating": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+ "dev": true,
+ "requires": {
+ "is-finite": "^1.0.0"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+ "dev": true
+ },
+ "resolve-pathname": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
+ "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
+ },
+ "rfdc": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
+ "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+ "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
+ "dev": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
+ },
+ "setprototypeof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "socket.io": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz",
+ "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==",
+ "dev": true,
+ "requires": {
+ "debug": "~3.1.0",
+ "engine.io": "~3.2.0",
+ "has-binary2": "~1.0.2",
+ "socket.io-adapter": "~1.1.0",
+ "socket.io-client": "2.1.1",
+ "socket.io-parser": "~3.2.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-adapter": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
+ "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=",
+ "dev": true
+ },
+ "socket.io-client": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz",
+ "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==",
+ "dev": true,
+ "requires": {
+ "backo2": "1.0.2",
+ "base64-arraybuffer": "0.1.5",
+ "component-bind": "1.0.0",
+ "component-emitter": "1.2.1",
+ "debug": "~3.1.0",
+ "engine.io-client": "~3.2.0",
+ "has-binary2": "~1.0.2",
+ "has-cors": "1.1.0",
+ "indexof": "0.0.1",
+ "object-component": "0.0.3",
+ "parseqs": "0.0.5",
+ "parseuri": "0.0.5",
+ "socket.io-parser": "~3.2.0",
+ "to-array": "0.1.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-parser": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
+ "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
+ "dev": true,
+ "requires": {
+ "component-emitter": "1.2.1",
+ "debug": "~3.1.0",
+ "isarray": "2.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "spdx-correct": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+ "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+ "dev": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "dev": true
+ },
+ "stream-browserify": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+ "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.1",
+ "readable-stream": "^2.0.2"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
+ "stream-http": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.0.tgz",
+ "integrity": "sha512-cuB6RgO7BqC4FBYzmnvhob5Do3wIdIsXAgGycHJnW+981gHqoYcYz9lqjJrk8WXRddbwPuqPYRl+bag6mYv4lw==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^3.0.6",
+ "xtend": "^4.0.0"
+ }
+ },
+ "streamroller": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz",
+ "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==",
+ "dev": true,
+ "requires": {
+ "async": "^2.6.2",
+ "date-format": "^2.0.0",
+ "debug": "^3.2.6",
+ "fs-extra": "^7.0.1",
+ "lodash": "^4.17.14"
+ }
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ }
+ },
+ "string.prototype.trimleft": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
+ "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
+ "string.prototype.trimright": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
+ "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "function-bind": "^1.1.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "dev": true,
+ "requires": {
+ "is-utf8": "^0.2.0"
+ }
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+ "dev": true,
+ "requires": {
+ "get-stdin": "^4.0.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "through2": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.0.tgz",
+ "integrity": "sha512-8B+sevlqP4OiCjonI1Zw03Sf8PuV1eRsYQgLad5eonILOdyeRsY27A/2Ze8IlvlMvq31OH+3fz/styI7Ya62yQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "2 || 3",
+ "xtend": "~4.0.1"
+ }
+ },
+ "timers-browserify": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+ "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+ "dev": true,
+ "requires": {
+ "setimmediate": "^1.0.4"
+ }
+ },
+ "tiny-invariant": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
+ "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
+ },
+ "tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
+ "to-array": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+ "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "dev": true
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+ "dev": true
+ },
+ "tty-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
+ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
+ "dev": true
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2"
+ }
+ },
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
+ "type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dev": true,
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ }
+ },
+ "typescript": {
+ "version": "3.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
+ "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
+ "dev": true
+ },
+ "uglify-js": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
+ "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "commander": "~2.20.0",
+ "source-map": "~0.6.1"
+ }
+ },
+ "ultron": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
+ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
+ "dev": true
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "dev": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "useragent": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
+ "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "4.1.x",
+ "tmp": "0.0.x"
+ }
+ },
+ "util": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.12.1.tgz",
+ "integrity": "sha512-MREAtYOp+GTt9/+kwf00IYoHZyjM8VU4aVrkzUlejyqaIjd2GztVl5V9hGXKlvBKE3gENn/FMfHE5v6hElXGcQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "is-arguments": "^1.0.4",
+ "is-generator-function": "^1.0.7",
+ "object.entries": "^1.1.0",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+ "dev": true
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "value-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
+ "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
+ },
+ "vm-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz",
+ "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==",
+ "dev": true
+ },
+ "void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=",
+ "dev": true
+ },
+ "wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+ "dev": true,
+ "requires": {
+ "defaults": "^1.0.3"
+ }
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wordwrap": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "ws": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
+ "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
+ "dev": true,
+ "requires": {
+ "async-limiter": "~1.0.0",
+ "safe-buffer": "~5.1.0",
+ "ultron": "~1.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ }
+ }
+ },
+ "xmlhttprequest-ssl": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
+ "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=",
+ "dev": true
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "13.2.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
+ "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==",
+ "dev": true,
+ "requires": {
+ "cliui": "^4.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "os-locale": "^3.1.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz",
+ "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "yargs-unparser": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz",
+ "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==",
+ "dev": true,
+ "requires": {
+ "flat": "^4.1.0",
+ "lodash": "^4.17.11",
+ "yargs": "^12.0.5"
+ },
+ "dependencies": {
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "12.0.5",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^3.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^11.1.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+ },
+ "yeast": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
+ "dev": true
+ }
+ }
+}
diff --git a/jslib/package.json b/jslib/package.json
new file mode 100644
index 00000000..0e37d696
--- /dev/null
+++ b/jslib/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "jslib",
+ "version": "0.1.0",
+ "description": "An internal JavaScript SDK for Dnote",
+ "types": "dist/types/index.d.ts",
+ "scripts": {
+ "clean": "rm -rf ./dist",
+ "build": "tsc",
+ "build:watch": "tsc --watch",
+ "test": "karma start ./karma.conf.js --single-run",
+ "test:watch": "karma start ./karma.conf.js --watch"
+ },
+ "author": "Monomax Software Pty Ltd",
+ "license": "GPL-3.0-or-later",
+ "dependencies": {
+ "@types/history": "^4.7.3",
+ "history": "^4.10.1",
+ "lodash": "^4.17.15",
+ "qs": "^6.9.0"
+ },
+ "devDependencies": {
+ "@types/mocha": "^5.2.7",
+ "chai": "^4.2.0",
+ "karma": "^4.3.0",
+ "karma-chrome-launcher": "^3.1.0",
+ "karma-mocha": "^1.3.0",
+ "karma-mocha-reporter": "^2.2.5",
+ "karma-typescript": "^4.1.1",
+ "mocha": "^6.2.0",
+ "prettier": "^1.18.2",
+ "typescript": "^3.6.3"
+ }
+}
diff --git a/web/src/libs/books.js b/jslib/src/helpers/books.ts
similarity index 82%
rename from web/src/libs/books.js
rename to jslib/src/helpers/books.ts
index d7d8f5f9..dd8e3de0 100644
--- a/web/src/libs/books.js
+++ b/jslib/src/helpers/books.ts
@@ -16,6 +16,8 @@
* along with Dnote. If not, see .
*/
+import { BookData } from '../operations/books';
+
// errBookNameNumeric is an error for book names that only contain numbers
export const errBookNameNumeric = new Error(
'The book name cannot contain only numbers'
@@ -26,6 +28,9 @@ export const errBookNameHasSpace = new Error(
'The book name cannot contain spaces'
);
+// errBookNameHasComma is an error for book names that have any comma
+export const errBookNameHasComma = new Error('The book name has comma');
+
// errBookNameReserved is an error incidating that the specified book name is reserved
export const errBookNameReserved = new Error('The book name is reserved');
@@ -34,7 +39,7 @@ const numberRegex = /^\d+$/;
const reservedBookNames = ['trash', 'conflicts'];
// validateBookName validates the given book name and throws error if not valid
-export function validateBookName(bookName) {
+export function validateBookName(bookName: string) {
if (reservedBookNames.indexOf(bookName) > -1) {
throw errBookNameReserved;
}
@@ -46,11 +51,15 @@ export function validateBookName(bookName) {
if (bookName.indexOf(' ') > -1) {
throw errBookNameHasSpace;
}
+
+ if (bookName.indexOf(',') > -1) {
+ throw errBookNameHasComma;
+ }
}
// checkDuplicate checks if the given book name has a duplicate in the given array
// of books
-export function checkDuplicate(books, bookName) {
+export function checkDuplicate(books: BookData[], bookName: string): boolean {
for (let i = 0; i < books.length; i++) {
const book = books[i];
diff --git a/jslib/src/helpers/books_test.ts b/jslib/src/helpers/books_test.ts
new file mode 100644
index 00000000..f8761f88
--- /dev/null
+++ b/jslib/src/helpers/books_test.ts
@@ -0,0 +1,184 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import { expect } from 'chai';
+import {
+ validateBookName,
+ checkDuplicate,
+ errBookNameNumeric,
+ errBookNameHasSpace,
+ errBookNameReserved,
+ errBookNameHasComma
+} from './books';
+
+describe('books lib', () => {
+ describe('validateBookName', () => {
+ const testCases = [
+ {
+ input: 'javascript',
+ expectedErr: null
+ },
+ {
+ input: 'node.js',
+ expectedErr: null
+ },
+ {
+ input: 'foo bar',
+ expectedErr: errBookNameHasSpace
+ },
+ {
+ input: '123',
+ expectedErr: errBookNameNumeric
+ },
+ {
+ input: '+123',
+ expectedErr: null
+ },
+ {
+ input: '-123',
+ expectedErr: null
+ },
+ {
+ input: '+javascript',
+ expectedErr: null
+ },
+ {
+ input: '0',
+ expectedErr: errBookNameNumeric
+ },
+ {
+ input: '0333',
+ expectedErr: errBookNameNumeric
+ },
+ {
+ input: ' javascript',
+ expectedErr: errBookNameHasSpace
+ },
+ {
+ input: 'java script',
+ expectedErr: errBookNameHasSpace
+ },
+ {
+ input: 'javascript (1)',
+ expectedErr: errBookNameHasSpace
+ },
+ {
+ input: 'javascript ',
+ expectedErr: errBookNameHasSpace
+ },
+ {
+ input: 'javascript (1) (2) (3)',
+ expectedErr: errBookNameHasSpace
+ },
+ {
+ input: ',',
+ expectedErr: errBookNameHasComma
+ },
+ {
+ input: 'foo,bar',
+ expectedErr: errBookNameHasComma
+ },
+ {
+ input: ',,,',
+ expectedErr: errBookNameHasComma
+ },
+
+ // reserved book names
+ {
+ input: 'trash',
+ expectedErr: errBookNameReserved
+ },
+ {
+ input: 'conflicts',
+ expectedErr: errBookNameReserved
+ }
+ ];
+
+ for (let i = 0; i < testCases.length; ++i) {
+ const tc = testCases[i];
+
+ it(`validates ${tc.input}`, () => {
+ const base = expect(() => validateBookName(tc.input));
+
+ if (tc.expectedErr) {
+ base.to.throw(tc.expectedErr);
+ } else {
+ base.to.not.throw();
+ }
+ });
+ }
+ });
+
+ describe('checkDuplicate', () => {
+ const golangBook = {
+ label: 'golang',
+ uuid: '04a0ead6-a450-44c2-b952-4d8ddsfdc70j',
+ usn: 10,
+ created_at: '2019-08-20T05:13:54.690438Z',
+ updated_at: '2019-08-20T05:13:54.690438Z'
+ };
+ const fooBook = {
+ label: 'foo',
+ uuid: '14a0ead6-a450-44c2-b952-4d8ddsfdc70j',
+ usn: 10,
+ created_at: '2019-08-20T05:13:54.690438Z',
+ updated_at: '2019-08-20T05:13:54.690438Z'
+ };
+ const barBook = {
+ label: 'bar',
+ uuid: '24a0ead6-a450-44c2-b952-4d8ddsfdc70j',
+ usn: 10,
+ created_at: '2019-08-20T05:13:54.690438Z',
+ updated_at: '2019-08-20T05:13:54.690438Z'
+ };
+ const fooBarBook = {
+ label: 'foo_bar',
+ uuid: '34a0ead6-a450-44c2-b952-4d8ddsfdc70j',
+ usn: 10,
+ created_at: '2019-08-20T05:13:54.690438Z',
+ updated_at: '2019-08-20T05:13:54.690438Z'
+ };
+
+ const testCases = [
+ {
+ books: [],
+ bookName: 'javascript',
+ expected: false
+ },
+ {
+ books: [golangBook, fooBarBook, fooBook],
+ bookName: 'bar1',
+ expected: false
+ },
+ {
+ books: [golangBook, fooBook, barBook],
+ bookName: 'bar',
+ expected: true
+ }
+ ];
+
+ for (let i = 0; i < testCases.length; ++i) {
+ const tc = testCases[i];
+
+ it(`checks duplicate for the test case ${i}`, () => {
+ const result = checkDuplicate(tc.books, tc.bookName);
+ expect(result).to.equal(tc.expected);
+ });
+ }
+ });
+});
diff --git a/jslib/src/helpers/filters.ts b/jslib/src/helpers/filters.ts
new file mode 100644
index 00000000..6a61ca9d
--- /dev/null
+++ b/jslib/src/helpers/filters.ts
@@ -0,0 +1,95 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import { parseSearchString } from './url';
+import { Queries } from './queries';
+
+export interface Filters {
+ queries: Queries;
+ page: number;
+}
+
+function compareBookArr(a1: string[], a2: string[]) {
+ if (a1.length !== a2.length) {
+ return false;
+ }
+
+ const a1Sorted = a1.sort();
+ const a2Sorted = a2.sort();
+
+ for (let i = 0; i < a1Sorted.length; ++i) {
+ if (a1Sorted[i] !== a2Sorted[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// getFiltersFromSearchStr unmarshals the given search string from the URL
+// into an object
+export function getFiltersFromSearchStr(search: string): Filters {
+ const searchObj = parseSearchString(search);
+
+ let bookVal;
+ if (typeof searchObj.book === 'string') {
+ bookVal = [searchObj.book];
+ } else if (searchObj.book === undefined) {
+ bookVal = [];
+ } else {
+ bookVal = searchObj.book;
+ }
+
+ const ret: Filters = {
+ queries: {
+ q: searchObj.q || '',
+ book: bookVal
+ },
+ page: parseInt(searchObj.page, 10) || 1
+ };
+
+ return ret;
+}
+
+// checkFilterEqual checks that the two given filters are equal
+export function checkFilterEqual(a: Filters, b: Filters): boolean {
+ return (
+ a.page === b.page &&
+ a.queries.q === b.queries.q &&
+ compareBookArr(a.queries.book, b.queries.book)
+ );
+}
+
+// toSearchObj transforms the filters into a search obj to be marshaled to a URL search string
+export function toSearchObj(filters: Filters): any {
+ const ret: any = {};
+
+ const { queries } = filters;
+
+ if (filters.page) {
+ ret.page = filters.page;
+ }
+ if (queries.q !== '') {
+ ret.q = queries.q;
+ }
+ if (queries.book.length > 0) {
+ ret.book = queries.book;
+ }
+
+ return ret;
+}
diff --git a/jslib/src/helpers/http.ts b/jslib/src/helpers/http.ts
new file mode 100644
index 00000000..f4d22540
--- /dev/null
+++ b/jslib/src/helpers/http.ts
@@ -0,0 +1,131 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+// module https.ts provides an interface to make HTTP requests and receive responses
+
+class ResponseError extends Error {
+ response: Response;
+}
+
+function checkStatus(response: Response): Response | Promise {
+ if (response.status >= 200 && response.status < 300) {
+ return response;
+ }
+
+ return response.text().then(body => {
+ const error = new ResponseError(body);
+ error.response = response;
+
+ throw error;
+ });
+}
+
+function parseJSON(response: Response): Promise {
+ if (response.headers.get('Content-Type') === 'application/json') {
+ return response.json() as Promise;
+ }
+
+ return Promise.resolve(null);
+}
+
+function request(path: string, options: RequestInit) {
+ return fetch(path, {
+ ...options
+ })
+ .then(checkStatus)
+ .then(res => {
+ return parseJSON(res);
+ });
+}
+
+function get(path: string, options = {}): Promise {
+ return request(path, {
+ method: 'GET',
+ ...options
+ });
+}
+
+function post(path: string, data: any, options = {}): Promise {
+ return request(path, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ ...options
+ });
+}
+
+function patch(path: string, data: any, options = {}) {
+ return request(path, {
+ method: 'PATCH',
+ body: JSON.stringify(data),
+ ...options
+ });
+}
+
+function put(path: string, data: any, options = {}) {
+ return request(path, {
+ method: 'PUT',
+ body: JSON.stringify(data),
+ ...options
+ });
+}
+
+function del(path: string, options = {}) {
+ return request(path, {
+ method: 'DELETE',
+ ...options
+ });
+}
+
+export interface HttpClientConfig {
+ pathPrefix: string;
+ baseUrl: string;
+}
+
+// getHttpClient returns an http client
+export function getHttpClient(c: HttpClientConfig) {
+ function transformPath(path: string): string {
+ let ret = path;
+
+ if (c.pathPrefix !== '') {
+ ret = `${c.pathPrefix}${ret}`;
+ }
+ if (c.baseUrl !== '') {
+ ret = `${c.baseUrl}${ret}`;
+ }
+
+ return ret;
+ }
+
+ return {
+ get: (path: string, options = {}) => {
+ return get(transformPath(path), options);
+ },
+ post: (path: string, data = {}, options = {}) => {
+ return post(transformPath(path), data, options);
+ },
+ patch: (path: string, data, options = {}) => {
+ return patch(transformPath(path), data, options);
+ },
+ put: (path: string, data, options = {}) => {
+ return put(transformPath(path), data, options);
+ },
+ del: (path: string, options = {}) => {
+ return del(transformPath(path), options);
+ }
+ };
+}
diff --git a/jslib/src/helpers/index.ts b/jslib/src/helpers/index.ts
new file mode 100644
index 00000000..75abb7eb
--- /dev/null
+++ b/jslib/src/helpers/index.ts
@@ -0,0 +1,19 @@
+import * as BooksHelpers from './books';
+import * as FiltersHelpers from './filters';
+import * as HttpHelpers from './http';
+import * as ObjHelpers from './obj';
+import * as QueriesHelpers from './queries';
+import * as SearchHelpers from './search';
+import * as SelectHelpers from './select';
+import * as UrlHelpers from './url';
+
+export {
+ BooksHelpers,
+ FiltersHelpers,
+ HttpHelpers,
+ ObjHelpers,
+ QueriesHelpers,
+ SearchHelpers,
+ SelectHelpers,
+ UrlHelpers,
+}
diff --git a/web/src/helpers/keyboard.js b/jslib/src/helpers/keyboard.ts
similarity index 94%
rename from web/src/helpers/keyboard.js
rename to jslib/src/helpers/keyboard.ts
index e409a7ce..8b58f5e2 100644
--- a/web/src/helpers/keyboard.js
+++ b/jslib/src/helpers/keyboard.ts
@@ -21,3 +21,6 @@ export const KEYCODE_UP = 38;
export const KEYCODE_ENTER = 13;
export const KEYCODE_ESC = 27;
export const KEYCODE_TAB = 9;
+
+// alphabet
+export const KEYCODE_LOWERCASE_B = 66;
diff --git a/web/src/libs/obj.js b/jslib/src/helpers/obj.ts
similarity index 70%
rename from web/src/libs/obj.js
rename to jslib/src/helpers/obj.ts
index 09ee5a90..42d87a1c 100644
--- a/web/src/libs/obj.js
+++ b/jslib/src/helpers/obj.ts
@@ -16,10 +16,13 @@
* along with Dnote. If not, see .
*/
-function filterObjKeys(obj, keys, filterFn) {
+type FilterFn = (val: any, key: any) => boolean;
+
+export function filterObjKeys(obj: object, filterFn: FilterFn) {
return Object.keys(obj)
.filter(key => {
- return filterFn(key);
+ const val = obj[key];
+ return filterFn(val, key);
})
.reduce((ret, key) => {
return {
@@ -32,14 +35,14 @@ function filterObjKeys(obj, keys, filterFn) {
// whitelist returns a new object whose keys are whitelisted by the given array
// of keys
export function whitelist(obj, keys) {
- return filterObjKeys(obj, keys, key => {
+ return filterObjKeys(obj, (val, key) => {
return keys.indexOf(key) > -1;
});
}
// blacklist returns a new object where key-val pairs are filtered out by keys
export function blacklist(obj, keys) {
- return filterObjKeys(obj, keys, key => {
+ return filterObjKeys(obj, (val, key) => {
return keys.indexOf(key) === -1;
});
}
@@ -48,3 +51,17 @@ export function blacklist(obj, keys) {
export function isEmptyObj(obj) {
return Object.getOwnPropertyNames(obj).length === 0;
}
+
+// removeKey returns a new object with the given key removed
+export function removeKey(obj: object, deleteKey: string) {
+ const keys = Object.keys(obj).filter(key => key !== deleteKey);
+
+ const ret = {};
+
+ for (let i = 0; i < keys.length; ++i) {
+ const key = keys[i];
+ ret[key] = obj[key];
+ }
+
+ return ret;
+}
diff --git a/web/src/libs/perf.js b/jslib/src/helpers/perf.ts
similarity index 90%
rename from web/src/libs/perf.js
rename to jslib/src/helpers/perf.ts
index 81ff6895..8655994c 100644
--- a/web/src/libs/perf.js
+++ b/jslib/src/helpers/perf.ts
@@ -16,7 +16,7 @@
* along with Dnote. If not, see .
*/
-export function debounce(func, wait, immediate) {
+export function debounce(func: Function, wait: number, immediate?: boolean) {
let timeout;
return (...args) => {
@@ -31,7 +31,7 @@ export function debounce(func, wait, immediate) {
timeout = setTimeout(later, wait);
if (callNow) {
- func.apply(context, args);
+ func.apply(this, args);
}
};
}
diff --git a/jslib/src/helpers/queries.ts b/jslib/src/helpers/queries.ts
new file mode 100644
index 00000000..d3c9879c
--- /dev/null
+++ b/jslib/src/helpers/queries.ts
@@ -0,0 +1,66 @@
+import { Location } from 'history';
+
+import { parseSearchString } from './url';
+import { removeKey } from './obj';
+import * as searchLib from './search';
+
+export interface Queries {
+ q: string;
+ book: string[];
+}
+
+function encodeQuery(keyword: string, value: string): string {
+ return `${keyword}:${value}`;
+}
+
+export const keywordBook = 'book';
+
+export const keywords = [keywordBook];
+
+// parse unmarshals the given string represesntation of the queries into an object
+export function parse(s: string): Queries {
+ const result = searchLib.parse(s, keywords);
+
+ let bookValue: string[];
+ const { book } = result.filters;
+ if (!book) {
+ bookValue = [];
+ } else if (typeof book === 'string') {
+ bookValue = [book];
+ } else {
+ bookValue = book;
+ }
+
+ let qValue: string;
+ if (result.text) {
+ qValue = result.text;
+ } else {
+ qValue = '';
+ }
+
+ return {
+ q: qValue,
+ book: bookValue
+ };
+}
+
+// stringify marshals the givne queries into a string format
+export function stringify(queries: Queries): string {
+ let ret = '';
+
+ if (queries.book.length > 0) {
+ for (let i = 0; i < queries.book.length; i++) {
+ const book = queries.book[i];
+
+ ret += encodeQuery(keywordBook, book);
+ ret += ' ';
+ }
+ }
+
+ if (queries.q !== '') {
+ const result = searchLib.parse(queries.q, keywords);
+ ret += result.text;
+ }
+
+ return ret;
+}
diff --git a/jslib/src/helpers/search.ts b/jslib/src/helpers/search.ts
new file mode 100644
index 00000000..7e79e093
--- /dev/null
+++ b/jslib/src/helpers/search.ts
@@ -0,0 +1,262 @@
+export enum TokenKind {
+ colon = 'COLON',
+ id = 'ID',
+ eof = 'EOF'
+}
+
+interface Token {
+ kind: TokenKind;
+ value?: string;
+}
+
+export enum NodeKind {
+ text = 'text',
+ filter = 'filter'
+}
+
+interface TextNode {
+ kind: NodeKind.text;
+ value: string;
+}
+
+interface FilterNode {
+ kind: NodeKind.filter;
+ keyword: string;
+ value: string;
+}
+
+type Node = TextNode | FilterNode;
+
+function nodeToString(node: Node): string {
+ if (node.kind === NodeKind.text) {
+ return node.value;
+ }
+ if (node.kind === NodeKind.filter) {
+ return `${node.keyword}:${node.value}`;
+ }
+
+ throw new Error('unknown node kind');
+}
+
+const whitespaceRegex = /^\s*$/;
+const charRegex = /^((?![\s:]).)*$/;
+
+function isSpace(c: string): boolean {
+ return whitespaceRegex.test(c);
+}
+
+function isChar(c: string): boolean {
+ return charRegex.test(c);
+}
+
+export function tokenize(s: string): Token[] {
+ const ret: Token[] = [];
+ let cursor = 0;
+
+ function colon() {
+ ret.push({ kind: TokenKind.colon });
+ cursor++;
+ }
+ function id() {
+ let text = '';
+ while (cursor <= s.length - 1 && isChar(s[cursor])) {
+ text += s[cursor];
+ cursor++;
+ }
+
+ ret.push({ kind: TokenKind.id, value: text });
+ }
+
+ while (cursor <= s.length - 1) {
+ const currentChar = s[cursor];
+
+ if (isSpace(currentChar)) {
+ cursor++;
+ } else if (currentChar === ':') {
+ colon();
+ } else if (isChar(currentChar)) {
+ id();
+ } else {
+ throw new Error(`invalid character ${currentChar}`);
+ }
+ }
+
+ ret.push({ kind: TokenKind.eof });
+
+ return ret;
+}
+
+class Parser {
+ toks: Token[];
+ cursor: number;
+ currentToken: Token;
+
+ constructor(s: string) {
+ this.toks = tokenize(s);
+ this.cursor = 0;
+ }
+
+ do(): Node[] {
+ /*
+ * expr: term (term)*
+ *
+ * term: filter | text
+ *
+ * filter: text COLON text
+ *
+ * text: ID | EOF
+ */
+ return this.expr();
+ }
+
+ eat(kind: TokenKind) {
+ const currentToken = this.getCurrentToken();
+
+ if (currentToken.kind !== kind) {
+ throw new Error(
+ `invalid syntax. Expected ${kind} got: ${currentToken.kind}`
+ );
+ }
+
+ this.cursor++;
+ }
+
+ getCurrentToken() {
+ return this.toks[this.cursor];
+ }
+
+ expr(): Node[] {
+ const nodes: Node[] = [];
+
+ while (this.getCurrentToken().kind !== TokenKind.eof) {
+ const n = this.term();
+ nodes.push(n);
+ }
+
+ return nodes;
+ }
+
+ term(): Node | null {
+ // try to parse filter and backtrack if not match
+ const n = this.filter();
+ if (n !== null) {
+ return n;
+ }
+
+ return this.str();
+ }
+
+ filter(): Node | null {
+ // save the current cursor for backtracking
+ const cursor = this.cursor;
+
+ const keyword = this.text({ maybe: true });
+ if (keyword === null) {
+ this.cursor = cursor;
+ return null;
+ }
+ if (this.getCurrentToken().kind === TokenKind.colon) {
+ this.eat(TokenKind.colon);
+ } else {
+ this.cursor = cursor;
+ return null;
+ }
+ const value = this.text({ maybe: true });
+ if (value === null) {
+ this.cursor = cursor;
+ return null;
+ }
+
+ return {
+ kind: NodeKind.filter,
+ keyword,
+ value
+ };
+ }
+
+ str(): Node | null {
+ const value = this.text();
+ if (value === null) {
+ return null;
+ }
+
+ return {
+ kind: NodeKind.text,
+ value
+ };
+ }
+
+ // text parses and returns a text. If 'maybe' option is true, it returns null
+ // if the current token cannot be parsed as a text (such scenario can happen when
+ // backtracking)
+ text(opts = { maybe: false }): string | null {
+ const currentToken = this.getCurrentToken();
+
+ if (currentToken.kind === TokenKind.eof) {
+ return null;
+ }
+
+ if (opts.maybe) {
+ if (currentToken.kind !== TokenKind.id) {
+ return null;
+ }
+ }
+
+ this.eat(TokenKind.id);
+
+ return currentToken.value;
+ }
+}
+
+interface Search {
+ text: string;
+ filters: {
+ [key: string]: string | string[];
+ };
+}
+
+export function parse(s: string, keywords: string[]): Search {
+ const p = new Parser(s);
+
+ const ret: Search = {
+ text: '',
+ filters: {}
+ };
+
+ function addText(t: string) {
+ if (ret.text !== '') {
+ ret.text += ' ';
+ }
+ ret.text += t;
+ }
+
+ function addFilter(key: string, val: string) {
+ const currentVal = ret.filters[key];
+
+ if (typeof currentVal === 'undefined') {
+ ret.filters[key] = val;
+ } else if (typeof currentVal === 'string') {
+ ret.filters[key] = [currentVal, val];
+ } else {
+ ret.filters[key] = [...currentVal, val];
+ }
+ }
+
+ const nodes = p.do();
+ for (let i = 0; i < nodes.length; i++) {
+ const n = nodes[i];
+
+ if (n.kind === NodeKind.text) {
+ addText(n.value);
+ } else if (n.kind === NodeKind.filter) {
+ // if keyword was not specified, treat as a text
+ if (keywords.indexOf(n.keyword) > -1) {
+ addFilter(n.keyword, n.value);
+ } else {
+ addText(nodeToString(n));
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/jslib/src/helpers/search_test.ts b/jslib/src/helpers/search_test.ts
new file mode 100644
index 00000000..ca9c8f90
--- /dev/null
+++ b/jslib/src/helpers/search_test.ts
@@ -0,0 +1,438 @@
+import { expect } from 'chai';
+import { TokenKind, tokenize, parse } from './search';
+
+describe('search.ts', () => {
+ describe('tokenize', () => {
+ const testCases = [
+ {
+ input: 'foo',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: 'foo'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ },
+ {
+ input: '123',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: '123'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ },
+ {
+ input: 'foo123',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: 'foo123'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ },
+ {
+ input: 'foo\tbar',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: 'foo'
+ },
+ {
+ kind: TokenKind.id,
+ value: 'bar'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ },
+ {
+ input: ' foo \tbar\t',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: 'foo'
+ },
+ {
+ kind: TokenKind.id,
+ value: 'bar'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ },
+ {
+ input: 'foo:bar',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: 'foo'
+ },
+ {
+ kind: TokenKind.colon
+ },
+ {
+ kind: TokenKind.id,
+ value: 'bar'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ },
+ {
+ input: '"foo" bar',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: '"foo"'
+ },
+ {
+ kind: TokenKind.id,
+ value: 'bar'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ },
+ {
+ input: '"foo:bar"',
+ tokens: [
+ {
+ kind: TokenKind.id,
+ value: '"foo'
+ },
+ {
+ kind: TokenKind.colon
+ },
+ {
+ kind: TokenKind.id,
+ value: 'bar"'
+ },
+ {
+ kind: TokenKind.eof
+ }
+ ]
+ }
+ ];
+
+ for (let i = 0; i < testCases.length; i++) {
+ const tc = testCases[i];
+
+ it(`tokenizes ${tc.input}`, () => {
+ const result = tokenize(tc.input);
+
+ expect(result).to.eql(tc.tokens);
+ });
+ }
+ });
+
+ describe('parse', () => {
+ function run(testCases) {
+ for (let i = 0; i < testCases.length; i++) {
+ const tc = testCases[i];
+
+ it(`keyword [${tc.keywords}] - parses ${tc.input} `, () => {
+ const result = parse(tc.input, tc.keywords);
+
+ expect(result).to.eql(tc.result);
+ });
+ }
+ }
+
+ describe('text only', () => {
+ const testCases = [
+ {
+ input: 'foo',
+ keywords: [],
+ result: {
+ text: 'foo',
+ filters: {}
+ }
+ },
+ {
+ input: '123',
+ keywords: [],
+ result: {
+ text: '123',
+ filters: {}
+ }
+ },
+ {
+ input: 'foo123',
+ keywords: [],
+ result: {
+ text: 'foo123',
+ filters: {}
+ }
+ },
+ {
+ input: '"',
+ keywords: [],
+ result: {
+ text: '"',
+ filters: {}
+ }
+ },
+ {
+ input: '""',
+ keywords: [],
+ result: {
+ text: '""',
+ filters: {}
+ }
+ },
+ {
+ input: `'`,
+ keywords: [],
+ result: {
+ text: `'`,
+ filters: {}
+ }
+ },
+ {
+ input: `''`,
+ keywords: [],
+ result: {
+ text: `''`,
+ filters: {}
+ }
+ },
+ {
+ input: `'foo:bar'`,
+ keywords: [],
+ result: {
+ text: `'foo:bar'`,
+ filters: {}
+ }
+ },
+ {
+ input: 'foo bar',
+ keywords: [],
+ result: {
+ text: 'foo bar',
+ filters: {}
+ }
+ },
+ {
+ input: ' foo \t bar ',
+ keywords: [],
+ result: {
+ text: 'foo bar',
+ filters: {}
+ }
+ },
+ {
+ input: '"foo:bar"',
+ keywords: ['foo'],
+ result: {
+ text: '"foo:bar"',
+ filters: {}
+ }
+ },
+ {
+ input: '"foo:bar""',
+ keywords: ['foo'],
+ result: {
+ text: '"foo:bar""',
+ filters: {}
+ }
+ },
+ {
+ input: '"foo:bar""""',
+ keywords: ['foo'],
+ result: {
+ text: '"foo:bar""""',
+ filters: {}
+ }
+ }
+ ];
+
+ run(testCases);
+ });
+
+ describe('filter only', () => {
+ const testCases = [
+ {
+ input: 'foo:bar',
+ keywords: ['foo'],
+ result: {
+ text: '',
+ filters: {
+ foo: 'bar'
+ }
+ }
+ },
+ {
+ input: '123:bar',
+ keywords: ['123'],
+ result: {
+ text: '',
+ filters: {
+ '123': 'bar'
+ }
+ }
+ },
+ {
+ input: 'foo123:bar',
+ keywords: ['foo123'],
+ result: {
+ text: '',
+ filters: {
+ foo123: 'bar'
+ }
+ }
+ },
+ {
+ input: '123:456',
+ keywords: ['123'],
+ result: {
+ text: '',
+ filters: {
+ '123': '456'
+ }
+ }
+ },
+ {
+ input: 'foo:bar baz:quz 123:qux',
+ keywords: ['foo'],
+ result: {
+ text: 'baz:quz 123:qux',
+ filters: {
+ foo: 'bar'
+ }
+ }
+ },
+ {
+ input: 'foo:bar baz:quz',
+ keywords: ['foo'],
+ result: {
+ text: 'baz:quz',
+ filters: {
+ foo: 'bar'
+ }
+ }
+ },
+ {
+ input: 'foo:bar baz:quz',
+ keywords: ['bar'],
+ result: {
+ text: 'foo:bar baz:quz',
+ filters: {}
+ }
+ },
+ {
+ input: 'foo:bar baz:quz',
+ keywords: ['foo', 'baz'],
+ result: {
+ text: '',
+ filters: {
+ foo: 'bar',
+ baz: 'quz'
+ }
+ }
+ },
+ {
+ input: 'foo:bar',
+ keywords: [],
+ result: {
+ text: 'foo:bar',
+ filters: {}
+ }
+ },
+ {
+ input: 'foo:bar baz:quz',
+ keywords: [],
+ result: {
+ text: 'foo:bar baz:quz',
+ filters: {}
+ }
+ },
+ {
+ input: 'foo:bar foo:baz',
+ keywords: ['foo'],
+ result: {
+ text: '',
+ filters: {
+ foo: ['bar', 'baz']
+ }
+ }
+ }
+ ];
+
+ run(testCases);
+ });
+
+ describe('text and filter', () => {
+ const testCases = [
+ {
+ input: 'foo:bar baz',
+ keywords: ['foo'],
+ result: {
+ text: 'baz',
+ filters: {
+ foo: 'bar'
+ }
+ }
+ },
+ {
+ input: 'foo:bar baz quz:qux1 ',
+ keywords: ['foo', 'quz'],
+ result: {
+ text: 'baz',
+ filters: {
+ foo: 'bar',
+ quz: 'qux1'
+ }
+ }
+ },
+ {
+ input: 'foo:bar baz quz:qux1 qux',
+ keywords: ['foo', 'quz'],
+ result: {
+ text: 'baz qux',
+ filters: {
+ foo: 'bar',
+ quz: 'qux1'
+ }
+ }
+ },
+ {
+ input: 'foo:bar baz quz:qux1 qux "quux:fooz"',
+ keywords: ['foo', 'quz'],
+ result: {
+ text: 'baz qux "quux:fooz"',
+ filters: {
+ foo: 'bar',
+ quz: 'qux1'
+ }
+ }
+ },
+ {
+ input: 'foo:bar baz quz:qux1 qux "quux:fooz"',
+ keywords: ['foo', 'quux'],
+ result: {
+ text: 'baz quz:qux1 qux "quux:fooz"',
+ filters: {
+ foo: 'bar'
+ }
+ }
+ }
+ ];
+
+ run(testCases);
+ });
+ });
+});
diff --git a/jslib/src/helpers/select.ts b/jslib/src/helpers/select.ts
new file mode 100644
index 00000000..a474d116
--- /dev/null
+++ b/jslib/src/helpers/select.ts
@@ -0,0 +1,62 @@
+import { BookData } from '../operations/books';
+
+// Option represents an option in a selection list
+export interface Option {
+ label: string;
+ value: string;
+}
+
+// optionValueCreate is the value of the option for creating a new option
+export const optionValueCreate = 'create-new-option';
+
+// filterOptions returns a new array of options based on the given filter criteria
+export function filterOptions(
+ options: Option[],
+ term: string,
+ creatable: boolean
+): Option[] {
+ if (!term) {
+ return options;
+ }
+
+ const ret = [];
+ const searchReg = new RegExp(`${term}`, 'i');
+ let hit = null;
+
+ for (let i = 0; i < options.length; i++) {
+ const option = options[i];
+
+ if (option.label === term) {
+ hit = option;
+ } else if (searchReg.test(option.label) && option.value !== '') {
+ ret.push(option);
+ }
+ }
+
+ // if there is an exact match, display the option at the top
+ // otherwise, display a creatable option at the bottom
+ if (hit) {
+ ret.unshift(hit);
+ } else if (creatable) {
+ // creatable option has a value of an empty string
+ ret.push({ label: term, value: '' });
+ }
+
+ return ret;
+}
+
+// booksToOptions returns an array of options for select ui, given an array of books
+export function booksToOptions(books: BookData[]): Option[] {
+ const ret = [];
+
+ for (let i = 0; i < books.length; ++i) {
+ const book = books[i];
+
+ ret.push({
+ label: book.label,
+ value: book.uuid
+ });
+ }
+
+ return ret;
+}
diff --git a/web/src/libs/url.js b/jslib/src/helpers/url.ts
similarity index 67%
rename from web/src/libs/url.js
rename to jslib/src/helpers/url.ts
index 73c49185..055a1416 100644
--- a/web/src/libs/url.js
+++ b/jslib/src/helpers/url.ts
@@ -19,10 +19,11 @@
import qs from 'qs';
import isArray from 'lodash/isArray';
import omitBy from 'lodash/omitBy';
+import { Location } from 'history';
// getPath returns a path optionally suffixed by query string
-export function getPath(path, queryObj) {
- const queryStr = qs.stringify(queryObj);
+export function getPath(path, queryObj): string {
+ const queryStr = qs.stringify(queryObj, { arrayFormat: 'repeat' });
if (!queryStr) {
return path;
@@ -33,42 +34,35 @@ export function getPath(path, queryObj) {
// getPathFromLocation returns a full path based on the location object used by
// React Router
-export function getPathFromLocation(location) {
+export function getPathFromLocation(location): string {
const { pathname, search } = location;
return `${pathname}${search}`;
}
-/**
- * parseSearchString parses the 'search' string in `location` object provided
- * by React Router.
- *
- * @param searchStr {String} - in a form of "?foo=bar&baz=1"
- * @return {Object} - in a form of "{foo: "bar", baz: "1"}"
- */
-export function parseSearchString(searchStr) {
- if (!searchStr || searchStr === '') {
+// parseSearchString parses the 'search' string in `location` object provided
+// by React Router.
+export function parseSearchString(search: string): any {
+ if (!search || search === '') {
return {};
}
// drop the leading '?'
- const queryStr = searchStr.substring(1);
+ const queryStr = search.substring(1);
return qs.parse(queryStr);
}
-/**
- * addQueryToLocation returns a new location object for react-router given the
- * new `queryKey` and `val` to be set in loation.query.
- * If there exists the given key in the query object, addQueryToLocation sets its
- * value to be an array containing the old value and the new value.
- * Otherwise the value for the key is set to the `val`.
- *
- * @param location {Object} - location object from react-router
- * @param queryKey {String} - the new query key to be set in location.query
- * @param val {String} - the value corresponding to queryKey
- * @param override {Boolean} - whether to override any existing param
- */
-export function addQueryToLocation(location, queryKey, val, override = true) {
+// addQueryToLocation returns a new location object for react-router given the
+// new `queryKey` and `val` to be set in loation.query.
+// If there exists the given key in the query object, addQueryToLocation sets its
+// value to be an array containing the old value and the new value.
+// Otherwise the value for the key is set to the `val`.
+export function addQueryToLocation(
+ location: Location,
+ queryKey: string,
+ val: string,
+ override = true
+): Location {
const queryObj = parseSearchString(location.search);
const existingParam = queryObj[queryKey];
@@ -94,11 +88,13 @@ export function addQueryToLocation(location, queryKey, val, override = true) {
};
}
-/**
- * removeQueryFromLocation returns a new location object without the queryKey
- * and val
- */
-export function removeQueryFromLocation(location, queryKey, val) {
+// removeQueryFromLocation returns a new location object without the queryKey
+// and val
+export function removeQueryFromLocation(
+ location: Location,
+ queryKey: string,
+ val?: string
+): Location {
const queryObj = parseSearchString(location.search);
const existingParam = queryObj[queryKey];
if (!existingParam) {
@@ -131,7 +127,7 @@ export function removeQueryFromLocation(location, queryKey, val) {
};
}
-export function getReferrer(location) {
+export function getReferrer(location: Location): string {
const queryObj = parseSearchString(location.search);
const { referrer } = queryObj;
diff --git a/jslib/src/index.ts b/jslib/src/index.ts
new file mode 100644
index 00000000..81a5bbea
--- /dev/null
+++ b/jslib/src/index.ts
@@ -0,0 +1,7 @@
+import * as Helpers from './helpers';
+import * as Operations from './helpers';
+
+export {
+ Helpers,
+ Operations
+};
diff --git a/jslib/src/operations/books.ts b/jslib/src/operations/books.ts
new file mode 100644
index 00000000..1d6218d3
--- /dev/null
+++ b/jslib/src/operations/books.ts
@@ -0,0 +1,59 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import initBooksService from '../services/books';
+import { HttpClientConfig } from '../helpers/http';
+
+export type BookData = {
+ uuid: string;
+ usn: number;
+ created_at: string;
+ updated_at: string;
+ label: string;
+};
+
+export interface CreateParams {
+ name: string;
+}
+
+export default function init(c: HttpClientConfig) {
+ const booksService = initBooksService(c);
+
+ return {
+ get: (bookUUID: string) => {
+ return booksService.get(bookUUID);
+ },
+
+ // create creates an encrypted book. It returns a promise that resolves with
+ // a decrypted book.
+ create: (payload: CreateParams): Promise => {
+ return booksService.create(payload).then(res => {
+ return res.book;
+ });
+ },
+
+ fetch: (params = {}) => {
+ return booksService.fetch(params);
+ },
+
+ // remove deletes the book with the given uuid
+ remove: (bookUUID: string) => {
+ return booksService.remove(bookUUID);
+ }
+ };
+}
diff --git a/jslib/src/operations/index.ts b/jslib/src/operations/index.ts
new file mode 100644
index 00000000..1d22b707
--- /dev/null
+++ b/jslib/src/operations/index.ts
@@ -0,0 +1,15 @@
+import { HttpClientConfig } from '../helpers/http';
+import initBooksOperation from './books';
+import initNotesOperation from './notes';
+
+// init initializes operations with the given http configuration
+// and returns an object of all services.
+export default function initOperations(c: HttpClientConfig) {
+ const booksOperation = initBooksOperation(c);
+ const notesOperation = initNotesOperation(c);
+
+ return {
+ books: booksOperation,
+ notes: notesOperation
+ };
+}
diff --git a/jslib/src/operations/notes.ts b/jslib/src/operations/notes.ts
new file mode 100644
index 00000000..ab4e8461
--- /dev/null
+++ b/jslib/src/operations/notes.ts
@@ -0,0 +1,68 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+// This module provides interfaces to perform operations. It abstarcts
+// the backend implementation and thus unifies the API for web and desktop clients.
+
+import initNotesService from '../services/notes';
+import { HttpClientConfig } from '../helpers/http';
+import { NoteData } from './types';
+import { Filters } from '../helpers/filters';
+
+export interface FetchOneParams {
+ q?: string;
+}
+
+export interface CreateParams {
+ bookUUID: string;
+ content: string;
+}
+
+export interface UpdateParams {
+ book_uuid?: string;
+ content?: string;
+ public?: boolean;
+}
+
+export default function init(c: HttpClientConfig) {
+ const notesService = initNotesService(c);
+
+ return {
+ fetch: (params: Filters) => {
+ return notesService.fetch(params);
+ },
+
+ fetchOne: (noteUUID: string, params: FetchOneParams = {}) => {
+ return notesService.fetchOne(noteUUID, params);
+ },
+
+ create: ({ bookUUID, content }: CreateParams) => {
+ return notesService.create({ book_uuid: bookUUID, content });
+ },
+
+ update: (noteUUID: string, input: UpdateParams): Promise => {
+ return notesService.update(noteUUID, input).then(res => {
+ return res.result;
+ });
+ },
+
+ remove: noteUUID => {
+ return notesService.remove(noteUUID);
+ }
+ };
+}
diff --git a/jslib/src/operations/types.ts b/jslib/src/operations/types.ts
new file mode 100644
index 00000000..420314d7
--- /dev/null
+++ b/jslib/src/operations/types.ts
@@ -0,0 +1,31 @@
+// NoteData represents a data for a note as returned by services.
+// The response from services need to conform to this interface.
+export interface NoteData {
+ uuid: string;
+ created_at: string;
+ updated_at: string;
+ content: string;
+ added_on: number;
+ public: boolean;
+ usn: number;
+ book: {
+ uuid: string;
+ label: string;
+ };
+ user: {
+ name: string;
+ uuid: string;
+ };
+}
+
+export interface EmailPrefData {
+ digestWeekly: boolean;
+}
+
+export interface UserData {
+ uuid: string;
+ email: string;
+ emailVerified: boolean;
+ pro: boolean;
+ classic: boolean;
+}
diff --git a/jslib/src/services/books.ts b/jslib/src/services/books.ts
new file mode 100644
index 00000000..8302220d
--- /dev/null
+++ b/jslib/src/services/books.ts
@@ -0,0 +1,81 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import qs from 'qs';
+import { getHttpClient, HttpClientConfig } from '../helpers/http';
+
+export interface BookFetchParams {
+ name?: string;
+ encrypted?: boolean;
+}
+
+export interface CreateParams {
+ name: string;
+}
+
+export interface CreatePayload {
+ book: {
+ uuid: string;
+ usn: number;
+ created_at: string;
+ updated_at: string;
+ label: string;
+ };
+}
+
+// TODO: type
+type updateParams = any;
+
+export default function init(config: HttpClientConfig) {
+ const client = getHttpClient(config);
+
+ return {
+ fetch: (queryObj: BookFetchParams = {}, opts = {}) => {
+ const baseURL = '/v3/books';
+
+ const queryStr = qs.stringify(queryObj);
+
+ let endpoint;
+ if (queryStr) {
+ endpoint = `${baseURL}?${queryStr}`;
+ } else {
+ endpoint = baseURL;
+ }
+
+ return client.get(endpoint, opts);
+ },
+
+ create: (payload: CreateParams, opts = {}) => {
+ return client.post('/v3/books', payload, opts);
+ },
+
+ remove: (uuid: string) => {
+ return client.del(`/v3/books/${uuid}`);
+ },
+
+ update: (uuid: string, payload: updateParams) => {
+ return client.patch(`/v3/books/${uuid}`, payload);
+ },
+
+ get: (bookUUID: string) => {
+ const endpoint = `/v3/books/${bookUUID}`;
+
+ return client.get(endpoint);
+ }
+ };
+}
diff --git a/web/src/components/Footer/Footer.js b/jslib/src/services/digests.ts
similarity index 53%
rename from web/src/components/Footer/Footer.js
rename to jslib/src/services/digests.ts
index a37b5957..4e04bdd7 100644
--- a/web/src/components/Footer/Footer.js
+++ b/jslib/src/services/digests.ts
@@ -16,34 +16,35 @@
* along with Dnote. If not, see .
*/
-import React from 'react';
-import { connect } from 'react-redux';
-import classnames from 'classnames';
+import { getHttpClient, HttpClientConfig } from '../helpers/http';
+import { getPath } from '../helpers/url';
-import AccountMenu from './AccountMenu';
+export default function init(config: HttpClientConfig) {
+ const client = getHttpClient(config);
-import styles from './Footer.module.scss';
-
-const Footer = ({ user, demo }) => {
- return (
-
- );
-};
-
-function mapStateToProps(state) {
return {
- user: state.auth.user.data
+ fetch: (digestUUID, { demo }) => {
+ let endpoint;
+ if (demo) {
+ endpoint = `/demo/digests/${digestUUID}`;
+ } else {
+ endpoint = `/digests/${digestUUID}`;
+ }
+
+ return client.get(endpoint);
+ },
+
+ fetchAll: ({ page, demo }) => {
+ let path;
+ if (demo) {
+ path = `/demo/digests`;
+ } else {
+ path = '/digests';
+ }
+
+ const endpoint = getPath(path, { page });
+
+ return client.get(endpoint);
+ }
};
}
-
-export default connect(mapStateToProps)(Footer);
diff --git a/jslib/src/services/index.ts b/jslib/src/services/index.ts
new file mode 100644
index 00000000..94b3cd18
--- /dev/null
+++ b/jslib/src/services/index.ts
@@ -0,0 +1,24 @@
+import { HttpClientConfig } from '../helpers/http';
+import initUsersService from './users';
+import initBooksService from './books';
+import initNotesService from './notes';
+import initDigestsService from './digests';
+import initPaymentService from './payment';
+
+// init initializes service helpers with the given http configuration
+// and returns an object of all services.
+export default function initServices(c: HttpClientConfig) {
+ const usersService = initUsersService(c);
+ const booksService = initBooksService(c);
+ const notesService = initNotesService(c);
+ const digestsService = initDigestsService(c);
+ const paymentService = initPaymentService(c);
+
+ return {
+ users: usersService,
+ books: booksService,
+ notes: notesService,
+ digests: digestsService,
+ payment: paymentService
+ };
+}
diff --git a/jslib/src/services/notes.ts b/jslib/src/services/notes.ts
new file mode 100644
index 00000000..a06772d4
--- /dev/null
+++ b/jslib/src/services/notes.ts
@@ -0,0 +1,108 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import { getPath } from '../helpers/url';
+import { getHttpClient, HttpClientConfig } from '../helpers/http';
+import { NoteData } from '../operations/types';
+import { Filters } from '../helpers/filters';
+
+export interface CreateParams {
+ book_uuid: string;
+ content: string;
+}
+
+export interface CreateResponse {
+ result: NoteData;
+}
+
+export interface UpdateParams {
+ book_uuid?: string;
+ content?: string;
+ public?: boolean;
+}
+
+export interface UpdateNoteResp {
+ status: number;
+ result: NoteData;
+}
+
+export interface FetchResponse {
+ notes: NoteData[];
+ total: number;
+}
+
+export interface FetchOneQuery {
+ q?: string;
+}
+
+type FetchOneResponse = NoteData;
+
+export default function init(config: HttpClientConfig) {
+ const client = getHttpClient(config);
+
+ return {
+ create: (params: CreateParams, opts = {}): Promise => {
+ return client.post('/v3/notes', params, opts);
+ },
+
+ update: (noteUUID: string, params: UpdateParams) => {
+ const endpoint = `/v3/notes/${noteUUID}`;
+
+ return client.patch(endpoint, params);
+ },
+
+ remove: (noteUUID: string) => {
+ const endpoint = `/v3/notes/${noteUUID}`;
+
+ return client.del(endpoint, {});
+ },
+
+ fetch: (filters: Filters) => {
+ const params: any = {
+ page: filters.page
+ };
+
+ const { queries } = filters;
+ if (queries.q) {
+ params.q = queries.q;
+ }
+ if (queries.book) {
+ params.book = queries.book;
+ }
+
+ const endpoint = getPath('/notes', params);
+
+ return client.get(endpoint, {});
+ },
+
+ fetchOne: (
+ noteUUID: string,
+ params: FetchOneQuery
+ ): Promise => {
+ const endpoint = getPath(`/notes/${noteUUID}`, params);
+
+ return client.get(endpoint, {});
+ },
+
+ classicFetch: () => {
+ const endpoint = '/classic/notes';
+
+ return client.get(endpoint, { credentials: 'include' });
+ }
+ };
+}
diff --git a/jslib/src/services/payment.ts b/jslib/src/services/payment.ts
new file mode 100644
index 00000000..40395d80
--- /dev/null
+++ b/jslib/src/services/payment.ts
@@ -0,0 +1,69 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import { getHttpClient, HttpClientConfig } from '../helpers/http';
+
+export default function init(config: HttpClientConfig) {
+ const client = getHttpClient(config);
+
+ return {
+ createSubscription: ({ source, country }) => {
+ const payload = {
+ source,
+ country
+ };
+
+ return client.post('/subscriptions', payload);
+ },
+
+ getSubscription: () => {
+ return client.get('/subscriptions');
+ },
+
+ cancelSubscription: ({ subscriptionId }) => {
+ const data = {
+ op: 'cancel',
+ stripe_subscription_id: subscriptionId
+ };
+
+ return client.patch('/subscriptions', data);
+ },
+
+ reactivateSubscription: ({ subscriptionId }) => {
+ const data = {
+ op: 'reactivate',
+ stripe_subscription_id: subscriptionId
+ };
+
+ return client.patch('/subscriptions', data);
+ },
+
+ getSource: () => {
+ return client.get('/stripe_source');
+ },
+
+ updateSource: ({ source, country }) => {
+ const payload = {
+ source,
+ country
+ };
+
+ return client.patch('/stripe_source', payload);
+ }
+ };
+}
diff --git a/jslib/src/services/types.ts b/jslib/src/services/types.ts
new file mode 100644
index 00000000..416c075e
--- /dev/null
+++ b/jslib/src/services/types.ts
@@ -0,0 +1,4 @@
+// Config is the configuration for the services
+export interface Config {
+ baseUrl: string;
+}
diff --git a/jslib/src/services/users.ts b/jslib/src/services/users.ts
new file mode 100644
index 00000000..80ca5ac0
--- /dev/null
+++ b/jslib/src/services/users.ts
@@ -0,0 +1,216 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import { getHttpClient, HttpClientConfig } from '../helpers/http';
+import { EmailPrefData, UserData } from '../operations/types';
+
+export interface UpdateProfileParams {
+ email: string;
+}
+
+export interface UpdatePasswordParams {
+ oldPassword: string;
+ newPassword: string;
+}
+
+export interface RegisterParams {
+ email: string;
+ password: string;
+}
+
+export interface SigninParams {
+ email: string;
+ password: string;
+}
+
+export interface SigninResponse {
+ key: string;
+ expires_at: number;
+}
+
+export interface GetEmailPreferenceParams {
+ // if not logged in, users can optionally make an authenticated request using a token
+ token?: string;
+}
+
+export interface classicPresigninPayload {
+ key: string;
+ expiresAt: number;
+ cipherKeyEnc: string;
+}
+
+export interface ResetPasswordParams {
+ token: string;
+ password: string;
+}
+
+export interface GetMeResponse {
+ user: {
+ uuid: string;
+ email: string;
+ email_verified: boolean;
+ pro: boolean;
+ classic: boolean;
+ };
+}
+
+export interface classicSetPasswordPayload {
+ password: string;
+}
+
+export default function init(config: HttpClientConfig) {
+ const client = getHttpClient(config);
+
+ return {
+ updateUser: ({ name }) => {
+ const payload = { name };
+
+ return client.patch('/account/profile', payload);
+ },
+
+ updateProfile: ({ email }: UpdateProfileParams) => {
+ const payload = {
+ email
+ };
+
+ return client.patch('/account/profile', payload);
+ },
+
+ updatePassword: ({ oldPassword, newPassword }: UpdatePasswordParams) => {
+ const payload = {
+ old_password: oldPassword,
+ new_password: newPassword
+ };
+
+ return client.patch('/account/password', payload);
+ },
+
+ register: (params: RegisterParams) => {
+ const payload = {
+ email: params.email,
+ password: params.password
+ };
+
+ return client.post('/v3/register', payload);
+ },
+
+ signin: (params: SigninParams) => {
+ const payload = {
+ email: params.email,
+ password: params.password
+ };
+
+ return client.post('/v3/signin', payload).then(resp => {
+ return {
+ key: resp.key,
+ expiresAt: resp.expires_at
+ };
+ });
+ },
+
+ signout: () => {
+ return client.post('/v3/signout');
+ },
+
+ sendResetPasswordEmail: ({ email }) => {
+ const payload = { email };
+
+ return client.post('/reset-token', payload);
+ },
+
+ sendEmailVerificationEmail: () => {
+ return client.post('/verification-token');
+ },
+
+ verifyEmail: ({ token }) => {
+ const payload = { token };
+
+ return client.patch('/verify-email', payload);
+ },
+
+ updateEmailPreference: ({ token, digestFrequency }) => {
+ const payload = { digest_weekly: digestFrequency === 'weekly' };
+
+ let endpoint = '/account/email-preference';
+ if (token) {
+ endpoint = `${endpoint}?token=${token}`;
+ }
+ return client.patch(endpoint, payload);
+ },
+
+ getEmailPreference: ({
+ token
+ }: GetEmailPreferenceParams): Promise => {
+ let endpoint = '/account/email-preference';
+ if (token) {
+ endpoint = `${endpoint}?token=${token}`;
+ }
+
+ return client.get(endpoint);
+ },
+
+ getMe: (): Promise => {
+ return client.get('/me').then(res => {
+ const { user } = res;
+
+ return {
+ uuid: user.uuid,
+ email: user.email,
+ emailVerified: user.email_verified,
+ pro: user.pro,
+ classic: user.classic
+ };
+ });
+ },
+
+ resetPassword: ({ token, password }: ResetPasswordParams) => {
+ const payload = { token, password };
+
+ return client.patch('/reset-password', payload);
+ },
+
+ // classic
+ classicPresignin: ({ email }) => {
+ return client.get(`/classic/presignin?email=${email}`);
+ },
+
+ classicSignin: ({ email, authKey }): Promise => {
+ const payload = { email, auth_key: authKey };
+
+ return client.post('/classic/signin', payload).then(resp => {
+ return {
+ key: resp.key,
+ expiresAt: resp.expires_at,
+ cipherKeyEnc: resp.cipher_key_enc
+ };
+ });
+ },
+
+ classicSetPassword: ({ password }: classicSetPasswordPayload) => {
+ const payload = {
+ password
+ };
+
+ return client.patch('/classic/set-password', payload);
+ },
+
+ classicCompleteMigrate: () => {
+ return client.patch('/classic/migrate', '');
+ }
+ };
+}
diff --git a/jslib/tsconfig.json b/jslib/tsconfig.json
new file mode 100644
index 00000000..cb7d0f64
--- /dev/null
+++ b/jslib/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "sourceMap": true,
+ "noImplicitAny": false,
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "target": "es6",
+ "jsx": "react",
+ "esModuleInterop": true,
+ "outDir": "dist",
+ "declaration": true,
+ "declarationDir": "dist/types"
+ }
+}
diff --git a/pkg/cli/client/client.go b/pkg/cli/client/client.go
index 712b7594..38fff768 100644
--- a/pkg/cli/client/client.go
+++ b/pkg/cli/client/client.go
@@ -31,7 +31,6 @@ import (
"time"
"github.com/dnote/dnote/pkg/cli/context"
- "github.com/dnote/dnote/pkg/cli/crypt"
"github.com/dnote/dnote/pkg/cli/log"
"github.com/pkg/errors"
)
@@ -126,7 +125,7 @@ type GetSyncStateResp struct {
func GetSyncState(ctx context.DnoteCtx) (GetSyncStateResp, error) {
var ret GetSyncStateResp
- res, err := doAuthorizedReq(ctx, "GET", "/v1/sync/state", "", nil)
+ res, err := doAuthorizedReq(ctx, "GET", "/v3/sync/state", "", nil)
if err != nil {
return ret, errors.Wrap(err, "constructing http request")
}
@@ -192,7 +191,7 @@ func GetSyncFragment(ctx context.DnoteCtx, afterUSN int) (GetSyncFragmentResp, e
v.Set("after_usn", strconv.Itoa(afterUSN))
queryStr := v.Encode()
- path := fmt.Sprintf("/v1/sync/fragment?%s", queryStr)
+ path := fmt.Sprintf("/v3/sync/fragment?%s", queryStr)
res, err := doAuthorizedReq(ctx, "GET", path, "", nil)
body, err := ioutil.ReadAll(res.Body)
@@ -230,20 +229,15 @@ type CreateBookResp struct {
// CreateBook creates a new book in the server
func CreateBook(ctx context.DnoteCtx, label string) (CreateBookResp, error) {
- encLabel, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(label))
- if err != nil {
- return CreateBookResp{}, errors.Wrap(err, "encrypting the label")
- }
-
payload := CreateBookPayload{
- Name: encLabel,
+ Name: label,
}
b, err := json.Marshal(payload)
if err != nil {
return CreateBookResp{}, errors.Wrap(err, "marshaling payload")
}
- res, err := doAuthorizedReq(ctx, "POST", "/v2/books", string(b), nil)
+ res, err := doAuthorizedReq(ctx, "POST", "/v3/books", string(b), nil)
if err != nil {
return CreateBookResp{}, errors.Wrap(err, "posting a book to the server")
}
@@ -267,20 +261,15 @@ type UpdateBookResp struct {
// UpdateBook updates a book in the server
func UpdateBook(ctx context.DnoteCtx, label, uuid string) (UpdateBookResp, error) {
- encName, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(label))
- if err != nil {
- return UpdateBookResp{}, errors.Wrap(err, "encrypting the content")
- }
-
payload := updateBookPayload{
- Name: &encName,
+ Name: &label,
}
b, err := json.Marshal(payload)
if err != nil {
return UpdateBookResp{}, errors.Wrap(err, "marshaling payload")
}
- endpoint := fmt.Sprintf("/v1/books/%s", uuid)
+ endpoint := fmt.Sprintf("/v3/books/%s", uuid)
res, err := doAuthorizedReq(ctx, "PATCH", endpoint, string(b), nil)
if err != nil {
return UpdateBookResp{}, errors.Wrap(err, "posting a book to the server")
@@ -302,7 +291,7 @@ type DeleteBookResp struct {
// DeleteBook deletes a book in the server
func DeleteBook(ctx context.DnoteCtx, uuid string) (DeleteBookResp, error) {
- endpoint := fmt.Sprintf("/v1/books/%s", uuid)
+ endpoint := fmt.Sprintf("/v3/books/%s", uuid)
res, err := doAuthorizedReq(ctx, "DELETE", endpoint, "", nil)
if err != nil {
return DeleteBookResp{}, errors.Wrap(err, "deleting a book in the server")
@@ -351,21 +340,16 @@ type RespNote struct {
// CreateNote creates a note in the server
func CreateNote(ctx context.DnoteCtx, bookUUID, content string) (CreateNoteResp, error) {
- encBody, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(content))
- if err != nil {
- return CreateNoteResp{}, errors.Wrap(err, "encrypting the content")
- }
-
payload := CreateNotePayload{
BookUUID: bookUUID,
- Body: encBody,
+ Body: content,
}
b, err := json.Marshal(payload)
if err != nil {
return CreateNoteResp{}, errors.Wrap(err, "marshaling payload")
}
- res, err := doAuthorizedReq(ctx, "POST", "/v2/notes", string(b), nil)
+ res, err := doAuthorizedReq(ctx, "POST", "/v3/notes", string(b), nil)
if err != nil {
return CreateNoteResp{}, errors.Wrap(err, "posting a book to the server")
}
@@ -392,14 +376,9 @@ type UpdateNoteResp struct {
// UpdateNote updates a note in the server
func UpdateNote(ctx context.DnoteCtx, uuid, bookUUID, content string, public bool) (UpdateNoteResp, error) {
- encBody, err := crypt.AesGcmEncrypt(ctx.CipherKey, []byte(content))
- if err != nil {
- return UpdateNoteResp{}, errors.Wrap(err, "encrypting the content")
- }
-
payload := updateNotePayload{
BookUUID: &bookUUID,
- Body: &encBody,
+ Body: &content,
Public: &public,
}
b, err := json.Marshal(payload)
@@ -407,7 +386,7 @@ func UpdateNote(ctx context.DnoteCtx, uuid, bookUUID, content string, public boo
return UpdateNoteResp{}, errors.Wrap(err, "marshaling payload")
}
- endpoint := fmt.Sprintf("/v1/notes/%s", uuid)
+ endpoint := fmt.Sprintf("/v3/notes/%s", uuid)
res, err := doAuthorizedReq(ctx, "PATCH", endpoint, string(b), nil)
if err != nil {
return UpdateNoteResp{}, errors.Wrap(err, "patching a note to the server")
@@ -429,7 +408,7 @@ type DeleteNoteResp struct {
// DeleteNote removes a note in the server
func DeleteNote(ctx context.DnoteCtx, uuid string) (DeleteNoteResp, error) {
- endpoint := fmt.Sprintf("/v1/notes/%s", uuid)
+ endpoint := fmt.Sprintf("/v3/notes/%s", uuid)
res, err := doAuthorizedReq(ctx, "DELETE", endpoint, "", nil)
if err != nil {
return DeleteNoteResp{}, errors.Wrap(err, "patching a note to the server")
@@ -451,7 +430,7 @@ type GetBooksResp []struct {
// GetBooks gets books from the server
func GetBooks(ctx context.DnoteCtx, sessionKey string) (GetBooksResp, error) {
- res, err := doAuthorizedReq(ctx, "GET", "/v1/books", "", nil)
+ res, err := doAuthorizedReq(ctx, "GET", "/v3/books", "", nil)
if err != nil {
return GetBooksResp{}, errors.Wrap(err, "making http request")
}
@@ -464,14 +443,14 @@ func GetBooks(ctx context.DnoteCtx, sessionKey string) (GetBooksResp, error) {
return resp, nil
}
-// PresigninResponse is a reponse from /v1/presignin endpoint
+// PresigninResponse is a reponse from /v3/presignin endpoint
type PresigninResponse struct {
Iteration int `json:"iteration"`
}
// GetPresignin gets presignin credentials
func GetPresignin(ctx context.DnoteCtx, email string) (PresigninResponse, error) {
- res, err := doReq(ctx, "GET", fmt.Sprintf("/v1/presignin?email=%s", email), "", nil)
+ res, err := doReq(ctx, "GET", fmt.Sprintf("/v3/presignin?email=%s", email), "", nil)
if err != nil {
return PresigninResponse{}, errors.Wrap(err, "making http request")
}
@@ -484,30 +463,29 @@ func GetPresignin(ctx context.DnoteCtx, email string) (PresigninResponse, error)
return resp, nil
}
-// SigninPayload is a payload for /v1/signin
+// SigninPayload is a payload for /v3/signin
type SigninPayload struct {
- Email string `json:"email"`
- AuthKey string `json:"auth_key"`
+ Email string `json:"email"`
+ Passowrd string `json:"password"`
}
-// SigninResponse is a response from /v1/signin endpoint
+// SigninResponse is a response from /v3/signin endpoint
type SigninResponse struct {
- Key string `json:"key"`
- ExpiresAt int64 `json:"expires_at"`
- CipherKeyEnc string `json:"cipher_key_enc"`
+ Key string `json:"key"`
+ ExpiresAt int64 `json:"expires_at"`
}
// Signin requests a session token
-func Signin(ctx context.DnoteCtx, email, authKey string) (SigninResponse, error) {
+func Signin(ctx context.DnoteCtx, email, password string) (SigninResponse, error) {
payload := SigninPayload{
- Email: email,
- AuthKey: authKey,
+ Email: email,
+ Passowrd: password,
}
b, err := json.Marshal(payload)
if err != nil {
return SigninResponse{}, errors.Wrap(err, "marshaling payload")
}
- res, err := doReq(ctx, "POST", "/v1/signin", string(b), nil)
+ res, err := doReq(ctx, "POST", "/v3/signin", string(b), nil)
if err != nil {
return SigninResponse{}, errors.Wrap(err, "making http request")
}
@@ -536,7 +514,7 @@ func Signout(ctx context.DnoteCtx, sessionKey string) error {
opts := requestOptions{
HTTPClient: &hc,
}
- _, err := doAuthorizedReq(ctx, "POST", "/v1/signout", "", &opts)
+ _, err := doAuthorizedReq(ctx, "POST", "/v3/signout", "", &opts)
if err != nil {
return errors.Wrap(err, "making http request")
}
diff --git a/pkg/cli/cmd/login/login.go b/pkg/cli/cmd/login/login.go
index f07483c4..5298f0b4 100644
--- a/pkg/cli/cmd/login/login.go
+++ b/pkg/cli/cmd/login/login.go
@@ -19,13 +19,11 @@
package login
import (
- "encoding/base64"
"strconv"
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
- "github.com/dnote/dnote/pkg/cli/crypt"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
@@ -51,38 +49,17 @@ func NewCmd(ctx context.DnoteCtx) *cobra.Command {
// Do dervies credentials on the client side and requests a session token from the server
func Do(ctx context.DnoteCtx, email, password string) error {
- presigninResp, err := client.GetPresignin(ctx, email)
- if err != nil {
- return errors.Wrap(err, "getting presiginin")
- }
-
- masterKey, authKey, err := crypt.MakeKeys([]byte(password), []byte(email), presigninResp.Iteration)
- if err != nil {
- return errors.Wrap(err, "making keys")
- }
-
- authKeyB64 := base64.StdEncoding.EncodeToString(authKey)
- signinResp, err := client.Signin(ctx, email, authKeyB64)
+ signinResp, err := client.Signin(ctx, email, password)
if err != nil {
return errors.Wrap(err, "requesting session")
}
- cipherKeyDec, err := crypt.AesGcmDecrypt(masterKey, signinResp.CipherKeyEnc)
- if err != nil {
- return errors.Wrap(err, "decrypting cipher key")
- }
-
- cipherKeyDecB64 := base64.StdEncoding.EncodeToString(cipherKeyDec)
-
db := ctx.DB
tx, err := db.Begin()
if err != nil {
return errors.Wrap(err, "beginning a transaction")
}
- if err := database.UpsertSystem(tx, consts.SystemCipherKey, cipherKeyDecB64); err != nil {
- return errors.Wrap(err, "saving enc key")
- }
if err := database.UpsertSystem(tx, consts.SystemSessionKey, signinResp.Key); err != nil {
return errors.Wrap(err, "saving session key")
}
@@ -97,7 +74,7 @@ func Do(ctx context.DnoteCtx, email, password string) error {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
- log.Plain("Welcome to Dnote Pro (https://dnote.io).\n")
+ log.Plain("Welcome to Dnote Pro (https://www.getdnote.com).\n")
var email, password string
if err := ui.PromptInput("email", &email); err != nil {
diff --git a/pkg/cli/cmd/logout/logout.go b/pkg/cli/cmd/logout/logout.go
index 1f3888e6..3b84ca6d 100644
--- a/pkg/cli/cmd/logout/logout.go
+++ b/pkg/cli/cmd/logout/logout.go
@@ -70,9 +70,6 @@ func Do(ctx context.DnoteCtx) error {
return errors.Wrap(err, "requesting logout")
}
- if err := database.DeleteSystem(tx, consts.SystemCipherKey); err != nil {
- return errors.Wrap(err, "deleting enc key")
- }
if err := database.DeleteSystem(tx, consts.SystemSessionKey); err != nil {
return errors.Wrap(err, "deleting session key")
}
diff --git a/pkg/cli/cmd/sync/sync.go b/pkg/cli/cmd/sync/sync.go
index 5707ef9c..e2acc51b 100644
--- a/pkg/cli/cmd/sync/sync.go
+++ b/pkg/cli/cmd/sync/sync.go
@@ -25,7 +25,6 @@ import (
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
- "github.com/dnote/dnote/pkg/cli/crypt"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/infra"
"github.com/dnote/dnote/pkg/cli/log"
@@ -97,7 +96,7 @@ func (l syncList) getLength() int {
// processFragments categorizes items in sync fragments into a sync list. It also decrypts any
// encrypted data in sync fragments.
-func processFragments(fragments []client.SyncFragment, cipherKey []byte) (syncList, error) {
+func processFragments(fragments []client.SyncFragment) (syncList, error) {
notes := map[string]client.SyncFragNote{}
books := map[string]client.SyncFragBook{}
expungedNotes := map[string]bool{}
@@ -107,23 +106,9 @@ func processFragments(fragments []client.SyncFragment, cipherKey []byte) (syncLi
for _, fragment := range fragments {
for _, note := range fragment.Notes {
- log.Debug("decrypting note %s\n", note.UUID)
- bodyDec, err := crypt.AesGcmDecrypt(cipherKey, note.Body)
- if err != nil {
- return syncList{}, errors.Wrapf(err, "decrypting body for note %s", note.UUID)
- }
-
- note.Body = string(bodyDec)
notes[note.UUID] = note
}
for _, book := range fragment.Books {
- log.Debug("decrypting book %s\n", book.UUID)
- labelDec, err := crypt.AesGcmDecrypt(cipherKey, book.Label)
- if err != nil {
- return syncList{}, errors.Wrapf(err, "decrypting label for book %s", book.UUID)
- }
-
- book.Label = string(labelDec)
books[book.UUID] = book
}
for _, uuid := range fragment.ExpungedBooks {
@@ -161,7 +146,7 @@ func getSyncList(ctx context.DnoteCtx, afterUSN int) (syncList, error) {
return syncList{}, errors.Wrap(err, "getting sync fragments")
}
- ret, err := processFragments(fragments, ctx.CipherKey)
+ ret, err := processFragments(fragments)
if err != nil {
return syncList{}, errors.Wrap(err, "making sync list")
}
@@ -902,7 +887,7 @@ func saveSyncState(tx *database.DB, serverTime int64, serverMaxUSN int) error {
func newRun(ctx context.DnoteCtx) infra.RunEFunc {
return func(cmd *cobra.Command, args []string) error {
- if ctx.SessionKey == "" || ctx.CipherKey == nil {
+ if ctx.SessionKey == "" {
return errors.New("not logged in")
}
diff --git a/pkg/cli/cmd/sync/sync_test.go b/pkg/cli/cmd/sync/sync_test.go
index 8959c30a..7d69df84 100644
--- a/pkg/cli/cmd/sync/sync_test.go
+++ b/pkg/cli/cmd/sync/sync_test.go
@@ -31,40 +31,38 @@ import (
"github.com/dnote/dnote/pkg/cli/client"
"github.com/dnote/dnote/pkg/cli/consts"
"github.com/dnote/dnote/pkg/cli/context"
- "github.com/dnote/dnote/pkg/cli/crypt"
"github.com/dnote/dnote/pkg/cli/database"
"github.com/dnote/dnote/pkg/cli/testutils"
"github.com/dnote/dnote/pkg/cli/utils"
"github.com/pkg/errors"
)
-var cipherKey = []byte("AES256Key-32Characters1234567890")
var dbPath = "../../tmp/.dnote.db"
func TestProcessFragments(t *testing.T) {
fragments := []client.SyncFragment{
- client.SyncFragment{
+ {
FragMaxUSN: 10,
UserMaxUSN: 10,
CurrentTime: 1550436136,
Notes: []client.SyncFragNote{
- client.SyncFragNote{
+ {
UUID: "45546de0-40ed-45cf-9bfc-62ce729a7d3d",
- Body: "7GgIppDdxDn+4DUoVoLXbncZDRqXGwbDVNF/eCssu+1BXMdq+HAziJHGgK7drdcIBtYDDXj0OwHz9dQDDOyWeNqkLWEIQ2Roygs229dRxdO3Z6ST+qSOr/9TTjDlFxydF5Ps7nAXdN9KVxH8FKIZDsxJ45qeLKpQK/6poAM39BCOiysqAXJQz9ngOJiqImAuftS6d/XhwX77QvnM91VCKK0tFmsMdDDw0J9QMwnlYU1CViHy1Hdhhcf9Ea38Mj4SCrWMPscXyP2fpAu5ukbIK3vS2pvbnH5vC8ZuvihrQif1BsiwfYmN981mLYs069Dn4B72qcXPwU7qrN3V0k57JGcAlTiEoOD5QowyraensQlR1doorLb43SjTiJLItougn5K5QPRiHuNxfv39pa7A0gKA1n/3UhG/SBuCpDuPYjwmBkvkzCKJNgpbLQ8p29JXMQcWrm4e9GfnVjMhAEtxttIta3MN6EcYG7cB1dJ04OLYVcJuRA==",
+ Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Donec ac libero efficitur, posuere dui non, egestas lectus.\n Aliquam urna ligula, sagittis eu volutpat vel, consequat et augue.\n\n Ut mi urna, dignissim a ex eget, venenatis accumsan sem. Praesent facilisis, ligula hendrerit auctor varius, mauris metus hendrerit dolor, sit amet pulvinar.",
},
- client.SyncFragNote{
+ {
UUID: "a25a5336-afe9-46c4-b881-acab911c0bc3",
- Body: "WGzcYA6kLuUFEU7HLTDJt7UWF7fEmbCPHfC16VBrAyfT2wDejXbIuFpU5L7g0aU=",
+ Body: "foo bar baz quz\nqux",
},
},
Books: []client.SyncFragBook{
- client.SyncFragBook{
+ {
UUID: "e8ac6f25-d95b-435a-9fae-094f7506a5ac",
- Label: "qBrSrAcnTUHu51bIrv6jSA/dNffr/kRlIg+MklxeQQ==",
+ Label: "foo",
},
- client.SyncFragBook{
+ {
UUID: "05fd8b95-ddcd-4071-9380-4358ffb8a436",
- Label: "uHWoBFdKT78gTkFR7qhyzZkrn59c8ktEa8idrLkksKzIQ3VVAXxq0QZp7Uc=",
+ Label: "foo-bar-baz-1000",
},
},
ExpungedNotes: []string{},
@@ -73,28 +71,28 @@ func TestProcessFragments(t *testing.T) {
}
// exec
- sl, err := processFragments(fragments, cipherKey)
+ sl, err := processFragments(fragments)
if err != nil {
t.Fatalf(errors.Wrap(err, "executing").Error())
}
expected := syncList{
Notes: map[string]client.SyncFragNote{
- "45546de0-40ed-45cf-9bfc-62ce729a7d3d": client.SyncFragNote{
+ "45546de0-40ed-45cf-9bfc-62ce729a7d3d": {
UUID: "45546de0-40ed-45cf-9bfc-62ce729a7d3d",
Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Donec ac libero efficitur, posuere dui non, egestas lectus.\n Aliquam urna ligula, sagittis eu volutpat vel, consequat et augue.\n\n Ut mi urna, dignissim a ex eget, venenatis accumsan sem. Praesent facilisis, ligula hendrerit auctor varius, mauris metus hendrerit dolor, sit amet pulvinar.",
},
- "a25a5336-afe9-46c4-b881-acab911c0bc3": client.SyncFragNote{
+ "a25a5336-afe9-46c4-b881-acab911c0bc3": {
UUID: "a25a5336-afe9-46c4-b881-acab911c0bc3",
Body: "foo bar baz quz\nqux",
},
},
Books: map[string]client.SyncFragBook{
- "e8ac6f25-d95b-435a-9fae-094f7506a5ac": client.SyncFragBook{
+ "e8ac6f25-d95b-435a-9fae-094f7506a5ac": {
UUID: "e8ac6f25-d95b-435a-9fae-094f7506a5ac",
Label: "foo",
},
- "05fd8b95-ddcd-4071-9380-4358ffb8a436": client.SyncFragBook{
+ "05fd8b95-ddcd-4071-9380-4358ffb8a436": {
UUID: "05fd8b95-ddcd-4071-9380-4358ffb8a436",
Label: "foo-bar-baz-1000",
},
@@ -1907,7 +1905,7 @@ func TestSendBooks(t *testing.T) {
// fire up a test server. It decrypts the payload for test purposes.
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.String() == "/v2/books" && r.Method == "POST" {
+ if r.URL.String() == "/v3/books" && r.Method == "POST" {
var payload client.CreateBookPayload
err := json.NewDecoder(r.Body).Decode(&payload)
@@ -1916,18 +1914,11 @@ func TestSendBooks(t *testing.T) {
return
}
- labelDec, err := crypt.AesGcmDecrypt(cipherKey, payload.Name)
- if err != nil {
- t.Fatalf(errors.Wrap(err, "decrypting label").Error())
- }
-
- labelDecStr := string(labelDec)
-
- createdLabels = append(createdLabels, labelDecStr)
+ createdLabels = append(createdLabels, payload.Name)
resp := client.CreateBookResp{
Book: client.RespBook{
- UUID: fmt.Sprintf("server-%s-uuid", labelDecStr),
+ UUID: fmt.Sprintf("server-%s-uuid", payload.Name),
},
}
@@ -1940,7 +1931,7 @@ func TestSendBooks(t *testing.T) {
}
p := strings.Split(r.URL.Path, "/")
- if len(p) == 4 && p[0] == "" && p[1] == "v1" && p[2] == "books" {
+ if len(p) == 4 && p[0] == "" && p[1] == "v3" && p[2] == "books" {
if r.Method == "PATCH" {
uuid := p[3]
updatesUUIDs = append(updatesUUIDs, uuid)
@@ -2033,7 +2024,7 @@ func TestSendBooks(t *testing.T) {
func TestSendBooks_isBehind(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.String() == "/v2/books" && r.Method == "POST" {
+ if r.URL.String() == "/v3/books" && r.Method == "POST" {
var payload client.CreateBookPayload
err := json.NewDecoder(r.Body).Decode(&payload)
@@ -2057,7 +2048,7 @@ func TestSendBooks_isBehind(t *testing.T) {
}
p := strings.Split(r.URL.Path, "/")
- if len(p) == 4 && p[0] == "" && p[1] == "v1" && p[2] == "books" {
+ if len(p) == 4 && p[0] == "" && p[1] == "v3" && p[2] == "books" {
if r.Method == "PATCH" {
resp := client.UpdateBookResp{
Book: client.RespBook{
@@ -2278,7 +2269,7 @@ func TestSendNotes(t *testing.T) {
// fire up a test server. It decrypts the payload for test purposes.
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.String() == "/v2/notes" && r.Method == "POST" {
+ if r.URL.String() == "/v3/notes" && r.Method == "POST" {
var payload client.CreateNotePayload
err := json.NewDecoder(r.Body).Decode(&payload)
@@ -2287,17 +2278,11 @@ func TestSendNotes(t *testing.T) {
return
}
- bodyDec, err := crypt.AesGcmDecrypt(cipherKey, payload.Body)
- if err != nil {
- t.Fatalf(errors.Wrap(err, "decrypting body").Error())
- }
- bodyDecStr := string(bodyDec)
-
- createdBodys = append(createdBodys, bodyDecStr)
+ createdBodys = append(createdBodys, payload.Body)
resp := client.CreateNoteResp{
Result: client.RespNote{
- UUID: fmt.Sprintf("server-%s-uuid", bodyDecStr),
+ UUID: fmt.Sprintf("server-%s-uuid", payload.Body),
},
}
@@ -2310,7 +2295,7 @@ func TestSendNotes(t *testing.T) {
}
p := strings.Split(r.URL.Path, "/")
- if len(p) == 4 && p[0] == "" && p[1] == "v1" && p[2] == "notes" {
+ if len(p) == 4 && p[0] == "" && p[1] == "v3" && p[2] == "notes" {
if r.Method == "PATCH" {
uuid := p[3]
updatedUUIDs = append(updatedUUIDs, uuid)
@@ -2413,7 +2398,7 @@ func TestSendNotes_addedOn(t *testing.T) {
// fire up a test server. It decrypts the payload for test purposes.
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.String() == "/v2/notes" && r.Method == "POST" {
+ if r.URL.String() == "/v3/notes" && r.Method == "POST" {
resp := client.CreateNoteResp{
Result: client.RespNote{
UUID: utils.GenerateUUID(),
@@ -2455,7 +2440,7 @@ func TestSendNotes_addedOn(t *testing.T) {
func TestSendNotes_isBehind(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.String() == "/v1/notes" && r.Method == "POST" {
+ if r.URL.String() == "/v3/notes" && r.Method == "POST" {
var payload client.CreateBookPayload
err := json.NewDecoder(r.Body).Decode(&payload)
@@ -2479,7 +2464,7 @@ func TestSendNotes_isBehind(t *testing.T) {
}
p := strings.Split(r.URL.Path, "/")
- if len(p) == 4 && p[0] == "" && p[1] == "v1" && p[2] == "notes" {
+ if len(p) == 4 && p[0] == "" && p[1] == "v3" && p[2] == "notes" {
if r.Method == "PATCH" {
resp := client.UpdateNoteResp{
Result: client.RespNote{
@@ -2928,18 +2913,18 @@ func TestCheckBookPristine(t *testing.T) {
func TestCheckNoteInList(t *testing.T) {
list := syncList{
Notes: map[string]client.SyncFragNote{
- "n1-uuid": client.SyncFragNote{
+ "n1-uuid": {
UUID: "n1-uuid",
},
- "n2-uuid": client.SyncFragNote{
+ "n2-uuid": {
UUID: "n2-uuid",
},
},
Books: map[string]client.SyncFragBook{
- "b1-uuid": client.SyncFragBook{
+ "b1-uuid": {
UUID: "b1-uuid",
},
- "b2-uuid": client.SyncFragBook{
+ "b2-uuid": {
UUID: "b2-uuid",
},
},
@@ -2990,18 +2975,18 @@ func TestCheckNoteInList(t *testing.T) {
func TestCheckBookInList(t *testing.T) {
list := syncList{
Notes: map[string]client.SyncFragNote{
- "n1-uuid": client.SyncFragNote{
+ "n1-uuid": {
UUID: "n1-uuid",
},
- "n2-uuid": client.SyncFragNote{
+ "n2-uuid": {
UUID: "n2-uuid",
},
},
Books: map[string]client.SyncFragBook{
- "b1-uuid": client.SyncFragBook{
+ "b1-uuid": {
UUID: "b1-uuid",
},
- "b2-uuid": client.SyncFragBook{
+ "b2-uuid": {
UUID: "b2-uuid",
},
},
@@ -3056,18 +3041,18 @@ func TestCleanLocalNotes(t *testing.T) {
list := syncList{
Notes: map[string]client.SyncFragNote{
- "n1-uuid": client.SyncFragNote{
+ "n1-uuid": {
UUID: "n1-uuid",
},
- "n2-uuid": client.SyncFragNote{
+ "n2-uuid": {
UUID: "n2-uuid",
},
},
Books: map[string]client.SyncFragBook{
- "b1-uuid": client.SyncFragBook{
+ "b1-uuid": {
UUID: "b1-uuid",
},
- "b2-uuid": client.SyncFragBook{
+ "b2-uuid": {
UUID: "b2-uuid",
},
},
@@ -3128,18 +3113,18 @@ func TestCleanLocalBooks(t *testing.T) {
list := syncList{
Notes: map[string]client.SyncFragNote{
- "n1-uuid": client.SyncFragNote{
+ "n1-uuid": {
UUID: "n1-uuid",
},
- "n2-uuid": client.SyncFragNote{
+ "n2-uuid": {
UUID: "n2-uuid",
},
},
Books: map[string]client.SyncFragBook{
- "b1-uuid": client.SyncFragBook{
+ "b1-uuid": {
UUID: "b1-uuid",
},
- "b2-uuid": client.SyncFragBook{
+ "b2-uuid": {
UUID: "b2-uuid",
},
},
diff --git a/pkg/cli/consts/consts.go b/pkg/cli/consts/consts.go
index 46f196da..1bb73584 100644
--- a/pkg/cli/consts/consts.go
+++ b/pkg/cli/consts/consts.go
@@ -41,8 +41,6 @@ var (
SystemLastMaxUSN = "last_max_usn"
// SystemLastUpgrade is the timestamp at which the system more recently checked for an upgrade
SystemLastUpgrade = "last_upgrade"
- // SystemCipherKey is the encryption key
- SystemCipherKey = "enc_key"
// SystemSessionKey is the session key
SystemSessionKey = "session_token"
// SystemSessionKeyExpiry is the timestamp at which the session key will expire
diff --git a/pkg/cli/context/ctx.go b/pkg/cli/context/ctx.go
index 29520eea..4df51408 100644
--- a/pkg/cli/context/ctx.go
+++ b/pkg/cli/context/ctx.go
@@ -33,7 +33,6 @@ type DnoteCtx struct {
DB *database.DB
SessionKey string
SessionKeyExpiry int64
- CipherKey []byte
Editor string
Clock clock.Clock
}
@@ -49,13 +48,5 @@ func Redact(ctx DnoteCtx) DnoteCtx {
}
ctx.SessionKey = sessionKey
- var cipherKey []byte
- if ctx.CipherKey != nil {
- cipherKey = []byte{1}
- } else {
- cipherKey = []byte{0}
- }
- ctx.CipherKey = cipherKey
-
return ctx
}
diff --git a/pkg/cli/context/operations.go b/pkg/cli/context/operations.go
deleted file mode 100644
index fe1aeb1f..00000000
--- a/pkg/cli/context/operations.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright (C) 2019 Monomax Software Pty Ltd
- *
- * This file is part of Dnote CLI.
- *
- * Dnote CLI is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Dnote CLI is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Dnote CLI. If not, see .
- */
-
-package context
-
-import (
- "encoding/base64"
-
- "github.com/dnote/dnote/pkg/cli/consts"
- "github.com/dnote/dnote/pkg/cli/database"
- "github.com/pkg/errors"
-)
-
-// GetCipherKey retrieves the cipher key and decode the base64 into bytes.
-func (ctx DnoteCtx) GetCipherKey() ([]byte, error) {
- tx, err := ctx.DB.Begin()
- if err != nil {
- return nil, errors.Wrap(err, "beginning transaction")
- }
-
- var cipherKeyB64 string
- err = database.GetSystem(tx, consts.SystemCipherKey, &cipherKeyB64)
- if err != nil {
- return []byte{}, errors.Wrap(err, "getting enc key")
- }
-
- cipherKey, err := base64.StdEncoding.DecodeString(cipherKeyB64)
- if err != nil {
- return nil, errors.Wrap(err, "decoding cipherKey from base64")
- }
-
- return cipherKey, nil
-}
diff --git a/pkg/cli/infra/init.go b/pkg/cli/infra/init.go
index 9242c1a3..e456cbe8 100644
--- a/pkg/cli/infra/init.go
+++ b/pkg/cli/infra/init.go
@@ -22,7 +22,6 @@ package infra
import (
"database/sql"
- "encoding/base64"
"fmt"
"os"
"os/user"
@@ -106,27 +105,18 @@ func Init(apiEndpoint, versionTag string) (*context.DnoteCtx, error) {
func SetupCtx(ctx context.DnoteCtx) (context.DnoteCtx, error) {
db := ctx.DB
- var sessionKey, cipherKeyB64 string
+ var sessionKey string
var sessionKeyExpiry int64
err := db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemSessionKey).Scan(&sessionKey)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key")
}
- err = db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemCipherKey).Scan(&cipherKeyB64)
- if err != nil && err != sql.ErrNoRows {
- return ctx, errors.Wrap(err, "finding sesison key")
- }
err = db.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemSessionKeyExpiry).Scan(&sessionKeyExpiry)
if err != nil && err != sql.ErrNoRows {
return ctx, errors.Wrap(err, "finding sesison key expiry")
}
- cipherKey, err := base64.StdEncoding.DecodeString(cipherKeyB64)
- if err != nil {
- return ctx, errors.Wrap(err, "decoding cipherKey from base64")
- }
-
cf, err := config.Read(ctx)
if err != nil {
return ctx, errors.Wrap(err, "reading config")
@@ -139,7 +129,6 @@ func SetupCtx(ctx context.DnoteCtx) (context.DnoteCtx, error) {
DB: ctx.DB,
SessionKey: sessionKey,
SessionKeyExpiry: sessionKeyExpiry,
- CipherKey: cipherKey,
APIEndpoint: cf.APIEndpoint,
Editor: cf.Editor,
Clock: clock.New(),
diff --git a/pkg/cli/migrate/migrate_test.go b/pkg/cli/migrate/migrate_test.go
index c14abed1..84bd0980 100644
--- a/pkg/cli/migrate/migrate_test.go
+++ b/pkg/cli/migrate/migrate_test.go
@@ -120,28 +120,28 @@ func TestRun_nonfresh(t *testing.T) {
"CREATE TABLE migrate_run_test ( name string )")
sequence := []migration{
- migration{
+ {
name: "v1",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v1 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v1")
return nil
},
},
- migration{
+ {
name: "v2",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v2 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v2")
return nil
},
},
- migration{
+ {
name: "v3",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v3 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v3")
return nil
},
},
- migration{
+ {
name: "v4",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v4 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v4")
@@ -200,21 +200,21 @@ func TestRun_fresh(t *testing.T) {
"CREATE TABLE migrate_run_test ( name string )")
sequence := []migration{
- migration{
+ {
name: "v1",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v1 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v1")
return nil
},
},
- migration{
+ {
name: "v2",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v2 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v2")
return nil
},
},
- migration{
+ {
name: "v3",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v3 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v3")
@@ -276,21 +276,21 @@ func TestRun_up_to_date(t *testing.T) {
database.MustExec(t, "inserting a schema", db, "INSERT INTO system (key, value) VALUES (?, ?)", tc.schemaKey, 3)
sequence := []migration{
- migration{
+ {
name: "v1",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v1 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v1")
return nil
},
},
- migration{
+ {
name: "v2",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v2 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v2")
return nil
},
},
- migration{
+ {
name: "v3",
run: func(ctx context.DnoteCtx, db *database.DB) error {
database.MustExec(t, "marking v3 completed", db, "INSERT INTO migrate_run_test (name) VALUES (?)", "v3")
@@ -1112,7 +1112,7 @@ func TestRemoteMigration1(t *testing.T) {
newCSSBookUUID := "new-css-book-uuid"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.String() == "/v1/books" {
+ if r.URL.String() == "/v3/books" {
res := []struct {
UUID string `json:"uuid"`
Label string `json:"label"`
diff --git a/pkg/cli/migrate/migrations.go b/pkg/cli/migrate/migrations.go
index 134dfee9..45b5a997 100644
--- a/pkg/cli/migrate/migrations.go
+++ b/pkg/cli/migrate/migrations.go
@@ -539,7 +539,7 @@ var lm12 = migration{
return errors.Wrap(err, "reading config")
}
- cf.APIEndpoint = "https://api.dnote.io"
+ cf.APIEndpoint = "https://api.getdnote.com"
err = config.Write(ctx, cf)
if err != nil {
diff --git a/pkg/cli/testutils/main.go b/pkg/cli/testutils/main.go
index 5bf9f4c1..6cb3d5c7 100644
--- a/pkg/cli/testutils/main.go
+++ b/pkg/cli/testutils/main.go
@@ -45,11 +45,9 @@ func Login(t *testing.T, ctx *context.DnoteCtx) {
database.MustExec(t, "inserting sessionKey", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKey, "someSessionKey")
database.MustExec(t, "inserting sessionKeyExpiry", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKeyExpiry, time.Now().Add(24*time.Hour).Unix())
- database.MustExec(t, "inserting cipherKey", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemCipherKey, "QUVTMjU2S2V5LTMyQ2hhcmFjdGVyczEyMzQ1Njc4OTA=")
ctx.SessionKey = "someSessionKey"
ctx.SessionKeyExpiry = time.Now().Add(24 * time.Hour).Unix()
- ctx.CipherKey = []byte("AES256Key-32Characters1234567890")
}
// RemoveDir cleans up the test env represented by the given context
diff --git a/pkg/server/api/handlers/auth.go b/pkg/server/api/handlers/auth.go
index 0cc613bc..6d579ca9 100644
--- a/pkg/server/api/handlers/auth.go
+++ b/pkg/server/api/handlers/auth.go
@@ -27,46 +27,29 @@ import (
"github.com/dnote/dnote/pkg/server/api/helpers"
"github.com/dnote/dnote/pkg/server/api/operations"
"github.com/dnote/dnote/pkg/server/database"
- "github.com/jinzhu/gorm"
- "github.com/markbates/goth"
- "github.com/markbates/goth/gothic"
+ "github.com/dnote/dnote/pkg/server/mailer"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
)
// Session represents user session
type Session struct {
- ID int `json:"id"`
- GithubName string `json:"github_name"`
- GithubAccountID string `json:"github_account_id"`
- APIKey string `json:"api_key"`
- Name string `json:"name"`
- Email string `json:"email"`
- EmailVerified bool `json:"email_verified"`
- Provider string `json:"provider"`
- Cloud bool `json:"cloud"`
- Legacy bool `json:"legacy"`
- Encrypted bool `json:"encrypted"`
- CipherKeyEnc string `json:"cipher_key_enc"`
+ UUID string `json:"uuid"`
+ Email string `json:"email"`
+ EmailVerified bool `json:"email_verified"`
+ Pro bool `json:"pro"`
+ Classic bool `json:"classic"`
}
func makeSession(user database.User, account database.Account) Session {
- legacy := account.AuthKeyHash == ""
+ classic := account.AuthKeyHash != ""
return Session{
- // TODO: remove ID and use UUID
- ID: user.ID,
- GithubName: account.Nickname,
- GithubAccountID: account.AccountID,
- APIKey: user.APIKey,
- Cloud: user.Cloud,
- Email: account.Email.String,
- EmailVerified: account.EmailVerified,
- Name: user.Name,
- Provider: account.Provider,
- Legacy: legacy,
- Encrypted: user.Encrypted,
- CipherKeyEnc: account.CipherKeyEnc,
+ UUID: user.UUID,
+ Pro: user.Cloud,
+ Email: account.Email.String,
+ EmailVerified: account.EmailVerified,
+ Classic: classic,
}
}
@@ -104,202 +87,141 @@ func (a *App) getMe(w http.ResponseWriter, r *http.Request) {
respondJSON(w, response)
}
-// OauthCallbackHandler handler
-func (a *App) oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
- githubUser, err := gothic.CompleteUserAuth(w, r)
- if err != nil {
- handleError(w, "completing user uath", err, http.StatusInternalServerError)
- return
- }
-
- db := database.DBConn
- tx := db.Begin()
-
- currentUser, err := findUserFromOauth(githubUser, tx)
- if err != nil {
- tx.Rollback()
- handleError(w, "Failed to upsert user", err, http.StatusInternalServerError)
- return
- }
- err = operations.TouchLastLoginAt(currentUser, tx)
- if err != nil {
- tx.Rollback()
- handleError(w, "touching login timestamp", err, http.StatusInternalServerError)
- return
- }
-
- tx.Commit()
-
- setAuthCookie(w, currentUser)
- http.Redirect(w, r, "/app/legacy/register", 301)
+type createResetTokenPayload struct {
+ Email string `json:"email"`
}
-// helpers
-// setAuthCookie sets 'api_key' cookie in the HTTP response for a given user
-func setAuthCookie(w http.ResponseWriter, currentUser database.User) {
- expire := time.Now().Add(time.Hour * 24 * 90)
- cookie := http.Cookie{
- Name: "api_key",
- Value: currentUser.APIKey,
- Expires: expire,
- Path: "/",
- HttpOnly: true,
- }
- http.SetCookie(w, &cookie)
-}
-
-func findUserFromOauth(oauthUser goth.User, tx *gorm.DB) (database.User, error) {
- var user database.User
- var account database.Account
-
- conn := tx.Where("account_id = ?", oauthUser.UserID).First(&account)
- if err := conn.Error; err != nil {
- return user, errors.Wrap(err, "finding account")
- }
-
- conn = tx.Where("id = ?", account.UserID).First(&user)
- if err := conn.Error; err != nil {
- return user, errors.Wrap(err, "finding user")
- }
-
- return user, nil
-}
-
-type legacyPasswordLoginPayload struct {
- Email string `json:"email"`
- Password string `json:"password"`
-}
-
-func (a *App) legacyPasswordLogin(w http.ResponseWriter, r *http.Request) {
+func (a *App) createResetToken(w http.ResponseWriter, r *http.Request) {
db := database.DBConn
- var params legacyPasswordLoginPayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handleError(w, "decoding payload", err, http.StatusInternalServerError)
+ var params createResetTokenPayload
+ if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
+ http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
var account database.Account
conn := db.Where("email = ?", params.Email).First(&account)
if conn.RecordNotFound() {
- http.Error(w, "Wrong email and password combination", http.StatusUnauthorized)
return
- } else if conn.Error != nil {
- handleError(w, "getting user", err, http.StatusInternalServerError)
+ }
+ if err := conn.Error; err != nil {
+ handleError(w, errors.Wrap(err, "finding account").Error(), nil, http.StatusInternalServerError)
return
}
- password := []byte(params.Password)
- err = bcrypt.CompareHashAndPassword([]byte(account.Password.String), password)
+ if account.AuthKeyHash != "" {
+ http.Error(w, "Please migrate your account from Dnote classic before resetting password", http.StatusBadRequest)
+ return
+ }
+
+ resetToken, err := generateResetToken()
if err != nil {
- http.Error(w, "Wrong email and password combination", http.StatusUnauthorized)
+ handleError(w, errors.Wrap(err, "generating token").Error(), nil, http.StatusInternalServerError)
return
}
- var user database.User
- err = db.Where("id = ?", account.UserID).First(&user).Error
- if err != nil {
- handleError(w, "finding user", err, http.StatusInternalServerError)
+ token := database.Token{
+ UserID: account.UserID,
+ Value: resetToken,
+ Type: database.TokenTypeResetPassword,
+ }
+
+ if err := db.Save(&token).Error; err != nil {
+ handleError(w, errors.Wrap(err, "saving token").Error(), nil, http.StatusInternalServerError)
return
}
- tx := db.Begin()
-
- err = operations.TouchLastLoginAt(user, tx)
- if err != nil {
- tx.Rollback()
- handleError(w, "touching login timestamp", err, http.StatusInternalServerError)
- return
- }
-
- tx.Commit()
-
- session := makeSession(user, account)
- response := struct {
- User Session `json:"user"`
+ subject := "Reset your password"
+ data := struct {
+ Subject string
+ Token string
}{
- User: session,
+ subject,
+ resetToken,
}
-
- setAuthCookie(w, user)
- respondJSON(w, response)
-}
-
-type legacyRegisterPayload struct {
- Email string `json:"email"`
- AuthKey string `json:"auth_key"`
- CipherKeyEnc string `json:"cipher_key_enc"`
- Iteration int `json:"iteration"`
-}
-
-func validateLegacyRegisterPayload(p legacyRegisterPayload) error {
- if p.Email == "" {
- return errors.New("email is required")
- }
- if p.AuthKey == "" {
- return errors.New("auth_key is required")
- }
- if p.CipherKeyEnc == "" {
- return errors.New("cipher_key_enc is required")
- }
- if p.Iteration == 0 {
- return errors.New("iteration is required")
- }
-
- return nil
-}
-
-func (a *App) legacyRegister(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
+ email := mailer.NewEmail("noreply@getdnote.com", []string{params.Email}, subject)
+ if err := email.ParseTemplate(mailer.EmailTypeResetPassword, data); err != nil {
+ handleError(w, errors.Wrap(err, "parsing template").Error(), nil, http.StatusInternalServerError)
return
}
+ if err := email.Send(); err != nil {
+ handleError(w, errors.Wrap(err, "sending email").Error(), nil, http.StatusInternalServerError)
+ return
+ }
+}
+
+type resetPasswordPayload struct {
+ Password string `json:"password"`
+ Token string `json:"token"`
+}
+
+func (a *App) resetPassword(w http.ResponseWriter, r *http.Request) {
db := database.DBConn
- var params legacyRegisterPayload
+ var params resetPasswordPayload
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
- handleError(w, "decoding payload", err, http.StatusInternalServerError)
+ http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
- if err := validateLegacyRegisterPayload(params); err != nil {
- handleError(w, "validating payload", err, http.StatusBadRequest)
+
+ var token database.Token
+ conn := db.Where("value = ? AND type =? AND used_at IS NULL", params.Token, database.TokenTypeResetPassword).First(&token)
+ if conn.RecordNotFound() {
+ http.Error(w, "invalid token", http.StatusBadRequest)
+ return
+ }
+ if err := conn.Error; err != nil {
+ handleError(w, errors.Wrap(err, "finding token").Error(), nil, http.StatusInternalServerError)
+ return
+ }
+
+ if token.UsedAt != nil {
+ http.Error(w, "invalid token", http.StatusBadRequest)
+ return
+ }
+
+ // Expire after 10 minutes
+ if time.Since(token.CreatedAt).Minutes() > 10 {
+ http.Error(w, "This link has been expired. Please request a new password reset link.", http.StatusGone)
return
}
tx := db.Begin()
- err := operations.LegacyRegisterUser(tx, user.ID, params.Email, params.AuthKey, params.CipherKeyEnc, params.Iteration)
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(params.Password), bcrypt.DefaultCost)
if err != nil {
tx.Rollback()
- handleError(w, "creating user", err, http.StatusBadRequest)
+ handleError(w, errors.Wrap(err, "hashing password").Error(), nil, http.StatusInternalServerError)
return
}
- tx.Commit()
-
var account database.Account
- if err := db.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
- handleError(w, "finding account", err, http.StatusInternalServerError)
+ if err := db.Where("user_id = ?", token.UserID).First(&account).Error; err != nil {
+ tx.Rollback()
+ handleError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError)
return
}
- respondWithSession(w, user.ID, account.CipherKeyEnc)
-}
-
-func (a *App) legacyMigrate(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- db := database.DBConn
-
- if err := db.Model(&user).Update("encrypted = ?", true).Error; err != nil {
- handleError(w, "updating user", err, http.StatusInternalServerError)
- return
- }
+ if err := tx.Model(&account).Update("password", string(hashedPassword)).Error; err != nil {
+ tx.Rollback()
+ handleError(w, errors.Wrap(err, "updating password").Error(), nil, http.StatusInternalServerError)
+ return
+ }
+ if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil {
+ tx.Rollback()
+ handleError(w, errors.Wrap(err, "updating password reset token").Error(), nil, http.StatusInternalServerError)
+ return
+ }
+
+ tx.Commit()
+
+ var user database.User
+ if err := db.Where("id = ?", account.UserID).First(&user).Error; err != nil {
+ handleError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError)
+ return
+ }
+
+ respondWithSession(w, user.ID, http.StatusOK)
}
diff --git a/pkg/server/api/handlers/auth_test.go b/pkg/server/api/handlers/auth_test.go
new file mode 100644
index 00000000..c41462f7
--- /dev/null
+++ b/pkg/server/api/handlers/auth_test.go
@@ -0,0 +1,312 @@
+package handlers
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/testutils"
+ "golang.org/x/crypto/bcrypt"
+)
+
+func TestGetMe(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+
+ dat := `{"email": "alice@example.com"}`
+ req := testutils.MakeReq(server, "POST", "/reset-token", dat)
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, u)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismtach")
+
+ var user database.User
+ testutils.MustExec(t, db.Where("id = ?", u.ID).First(&user), "finding user")
+ assert.Equal(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch")
+}
+
+func TestCreateResetToken(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+
+ dat := `{"email": "alice@example.com"}`
+ req := testutils.MakeReq(server, "POST", "/reset-token", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismtach")
+
+ var tokenCount int
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting tokens")
+
+ var resetToken database.Token
+ testutils.MustExec(t, db.Where("user_id = ? AND type = ?", u.ID, database.TokenTypeResetPassword).First(&resetToken), "finding reset token")
+
+ assert.Equal(t, tokenCount, 1, "reset_token count mismatch")
+ assert.NotEqual(t, resetToken.Value, nil, "reset_token value mismatch")
+ assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "reset_token UsedAt mismatch")
+ })
+
+ t.Run("nonexistent email", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+
+ dat := `{"email": "bob@example.com"}`
+ req := testutils.MakeReq(server, "POST", "/reset-token", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismtach")
+
+ var tokenCount int
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting tokens")
+ assert.Equal(t, tokenCount, 0, "reset_token count mismatch")
+ })
+}
+
+func TestResetPassword(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword")
+ tok := database.Token{
+ UserID: u.ID,
+ Value: "MivFxYiSMMA4An9dP24DNQ==",
+ Type: database.TokenTypeResetPassword,
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+ otherTok := database.Token{
+ UserID: u.ID,
+ Value: "somerandomvalue",
+ Type: database.TokenTypeEmailVerification,
+ }
+ testutils.MustExec(t, db.Save(&otherTok), "preparing another token")
+
+ dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "newpassword"}`
+ req := testutils.MakeReq(server, "PATCH", "/reset-password", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismatch")
+
+ var resetToken, verificationToken database.Token
+ var account database.Account
+ testutils.MustExec(t, db.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "finding reset token")
+ testutils.MustExec(t, db.Where("value = ?", "somerandomvalue").First(&verificationToken), "finding reset token")
+ testutils.MustExec(t, db.Where("id = ?", a.ID).First(&account), "finding account")
+
+ assert.NotEqual(t, resetToken.UsedAt, nil, "reset_token UsedAt mismatch")
+ passwordErr := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte("newpassword"))
+ assert.Equal(t, passwordErr, nil, "Password mismatch")
+ assert.Equal(t, verificationToken.UsedAt, (*time.Time)(nil), "verificationToken UsedAt mismatch")
+ })
+
+ t.Run("nonexistent token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+ tok := database.Token{
+ UserID: u.ID,
+ Value: "MivFxYiSMMA4An9dP24DNQ==",
+ Type: database.TokenTypeResetPassword,
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ dat := `{"token": "-ApMnyvpg59uOU5b-Kf5uQ==", "password": "oldpassword"}`
+ req := testutils.MakeReq(server, "PATCH", "/reset-password", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismatch")
+
+ var resetToken database.Token
+ var account database.Account
+ testutils.MustExec(t, db.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "finding reset token")
+ testutils.MustExec(t, db.Where("id = ?", a.ID).First(&account), "finding account")
+
+ assert.Equal(t, a.Password, account.Password, "password should not have been updated")
+ assert.Equal(t, a.Password, account.Password, "password should not have been updated")
+ assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "used_at should be nil")
+ })
+
+ t.Run("expired token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+ tok := database.Token{
+ UserID: u.ID,
+ Value: "MivFxYiSMMA4An9dP24DNQ==",
+ Type: database.TokenTypeResetPassword,
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+ testutils.MustExec(t, db.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-11)), "Failed to prepare reset_token created_at")
+
+ dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "oldpassword"}`
+ req := testutils.MakeReq(server, "PATCH", "/reset-password", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusGone, "Status code mismatch")
+
+ var resetToken database.Token
+ var account database.Account
+ testutils.MustExec(t, db.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "failed to find reset_token")
+ testutils.MustExec(t, db.Where("id = ?", a.ID).First(&account), "failed to find account")
+ assert.Equal(t, a.Password, account.Password, "password should not have been updated")
+ assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "used_at should be nil")
+ })
+
+ t.Run("used token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+
+ usedAt := time.Now().Add(time.Hour * -11).UTC()
+ tok := database.Token{
+ UserID: u.ID,
+ Value: "MivFxYiSMMA4An9dP24DNQ==",
+ Type: database.TokenTypeResetPassword,
+ UsedAt: &usedAt,
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+ testutils.MustExec(t, db.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-11)), "Failed to prepare reset_token created_at")
+
+ dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "oldpassword"}`
+ req := testutils.MakeReq(server, "PATCH", "/reset-password", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismatch")
+
+ var resetToken database.Token
+ var account database.Account
+ testutils.MustExec(t, db.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "failed to find reset_token")
+ testutils.MustExec(t, db.Where("id = ?", a.ID).First(&account), "failed to find account")
+ assert.Equal(t, a.Password, account.Password, "password should not have been updated")
+
+ if resetToken.UsedAt.Year() != usedAt.Year() ||
+ resetToken.UsedAt.Month() != usedAt.Month() ||
+ resetToken.UsedAt.Day() != usedAt.Day() ||
+ resetToken.UsedAt.Hour() != usedAt.Hour() ||
+ resetToken.UsedAt.Minute() != usedAt.Minute() ||
+ resetToken.UsedAt.Second() != usedAt.Second() {
+ t.Errorf("used_at should be %+v but got: %+v", usedAt, resetToken.UsedAt)
+ }
+ })
+
+ t.Run("using wrong type token: email_verification", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+ tok := database.Token{
+ UserID: u.ID,
+ Value: "MivFxYiSMMA4An9dP24DNQ==",
+ Type: database.TokenTypeEmailVerification,
+ }
+ testutils.MustExec(t, db.Save(&tok), "Failed to prepare reset_token")
+ testutils.MustExec(t, db.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-11)), "Failed to prepare reset_token created_at")
+
+ dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "oldpassword"}`
+ req := testutils.MakeReq(server, "PATCH", "/reset-password", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismatch")
+
+ var resetToken database.Token
+ var account database.Account
+ testutils.MustExec(t, db.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "failed to find reset_token")
+ testutils.MustExec(t, db.Where("id = ?", a.ID).First(&account), "failed to find account")
+
+ assert.Equal(t, a.Password, account.Password, "password should not have been updated")
+ assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "used_at should be nil")
+ })
+}
diff --git a/pkg/server/api/handlers/classic.go b/pkg/server/api/handlers/classic.go
new file mode 100644
index 00000000..7816520e
--- /dev/null
+++ b/pkg/server/api/handlers/classic.go
@@ -0,0 +1,276 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+package handlers
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/dnote/dnote/pkg/server/api/crypt"
+ "github.com/dnote/dnote/pkg/server/api/helpers"
+ "github.com/dnote/dnote/pkg/server/api/operations"
+ "github.com/dnote/dnote/pkg/server/api/presenters"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/log"
+ "github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
+)
+
+func (a *App) classicMigrate(w http.ResponseWriter, r *http.Request) {
+ user, ok := r.Context().Value(helpers.KeyUser).(database.User)
+ if !ok {
+ handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
+ return
+ }
+
+ db := database.DBConn
+
+ var account database.Account
+ if err := db.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
+ handleError(w, "finding account", err, http.StatusInternalServerError)
+ return
+ }
+
+ if err := db.Model(&account).
+ Update(map[string]interface{}{
+ "salt": "",
+ "auth_key_hash": "",
+ "cipher_key_enc": "",
+ "client_kdf_iteration": 0,
+ "server_kdf_iteration": 0,
+ }).Error; err != nil {
+ handleError(w, "updating account", err, http.StatusInternalServerError)
+ return
+ }
+}
+
+// PresigninResponse is a response for presignin
+type PresigninResponse struct {
+ Iteration int `json:"iteration"`
+}
+
+func (a *App) classicPresignin(w http.ResponseWriter, r *http.Request) {
+ db := database.DBConn
+
+ q := r.URL.Query()
+ email := q.Get("email")
+ if email == "" {
+ http.Error(w, "email is required", http.StatusBadRequest)
+ return
+ }
+
+ var account database.Account
+ conn := db.Where("email = ?", email).First(&account)
+ if !conn.RecordNotFound() && conn.Error != nil {
+ handleError(w, "getting user", conn.Error, http.StatusInternalServerError)
+ return
+ }
+
+ var response PresigninResponse
+ if conn.RecordNotFound() {
+ response = PresigninResponse{
+ Iteration: 100000,
+ }
+ } else {
+ response = PresigninResponse{
+ Iteration: account.ClientKDFIteration,
+ }
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(response); err != nil {
+ handleError(w, "encoding response", nil, http.StatusInternalServerError)
+ return
+ }
+}
+
+type classicSigninPayload struct {
+ Email string `json:"email"`
+ AuthKey string `json:"auth_key"`
+}
+
+func (a *App) classicSignin(w http.ResponseWriter, r *http.Request) {
+ db := database.DBConn
+
+ var params classicSigninPayload
+ if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
+ handleError(w, "decoding payload", err, http.StatusInternalServerError)
+ return
+ }
+
+ if params.Email == "" || params.AuthKey == "" {
+ http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
+ return
+ }
+
+ var account database.Account
+ conn := db.Where("email = ?", params.Email).First(&account)
+ if conn.RecordNotFound() {
+ http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
+ return
+ } else if err := conn.Error; err != nil {
+ handleError(w, "getting user", err, http.StatusInternalServerError)
+ return
+ }
+
+ authKeyHash := crypt.HashAuthKey(params.AuthKey, account.Salt, account.ServerKDFIteration)
+ if account.AuthKeyHash != authKeyHash {
+ log.WithFields(log.Fields{
+ "account_id": account.ID,
+ }).Error("Sign in password mismatch")
+ http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
+ return
+ }
+
+ session, err := operations.CreateSession(db, account.UserID)
+ if err != nil {
+ handleError(w, "creating session", nil, http.StatusBadRequest)
+ return
+ }
+
+ setSessionCookie(w, session.Key, session.ExpiresAt)
+
+ response := struct {
+ Key string `json:"key"`
+ ExpiresAt int64 `json:"expires_at"`
+ CipherKeyEnc string `json:"cipher_key_enc"`
+ }{
+ Key: session.Key,
+ ExpiresAt: session.ExpiresAt.Unix(),
+ CipherKeyEnc: account.CipherKeyEnc,
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(response); err != nil {
+ handleError(w, "encoding response", err, http.StatusInternalServerError)
+ return
+ }
+}
+
+func (a *App) classicGetMe(w http.ResponseWriter, r *http.Request) {
+ user, ok := r.Context().Value(helpers.KeyUser).(database.User)
+ if !ok {
+ handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
+ return
+ }
+
+ db := database.DBConn
+
+ var account database.Account
+ if err := db.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
+ handleError(w, "finding account", err, http.StatusInternalServerError)
+ return
+ }
+
+ legacy := account.AuthKeyHash == ""
+
+ type classicSession struct {
+ ID int `json:"id"`
+ GithubName string `json:"github_name"`
+ GithubAccountID string `json:"github_account_id"`
+ APIKey string `json:"api_key"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ EmailVerified bool `json:"email_verified"`
+ Provider string `json:"provider"`
+ Cloud bool `json:"cloud"`
+ Legacy bool `json:"legacy"`
+ Encrypted bool `json:"encrypted"`
+ CipherKeyEnc string `json:"cipher_key_enc"`
+ }
+
+ session := classicSession{
+ ID: user.ID,
+ GithubName: account.Nickname,
+ GithubAccountID: account.AccountID,
+ APIKey: user.APIKey,
+ Cloud: user.Cloud,
+ Email: account.Email.String,
+ EmailVerified: account.EmailVerified,
+ Name: user.Name,
+ Provider: account.Provider,
+ Legacy: legacy,
+ Encrypted: user.Encrypted,
+ CipherKeyEnc: account.CipherKeyEnc,
+ }
+
+ response := struct {
+ User classicSession `json:"user"`
+ }{
+ User: session,
+ }
+
+ respondJSON(w, response)
+}
+
+type classicSetPasswordPayload struct {
+ Password string
+}
+
+func (a *App) classicSetPassword(w http.ResponseWriter, r *http.Request) {
+ user, ok := r.Context().Value(helpers.KeyUser).(database.User)
+ if !ok {
+ handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
+ return
+ }
+
+ db := database.DBConn
+
+ var params classicSetPasswordPayload
+ if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
+ handleError(w, "decoding payload", err, http.StatusInternalServerError)
+ return
+ }
+
+ var account database.Account
+ if err := db.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
+ handleError(w, "getting user", nil, http.StatusInternalServerError)
+ return
+ }
+
+ hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(params.Password), bcrypt.DefaultCost)
+ if err != nil {
+ http.Error(w, errors.Wrap(err, "hashing password").Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if err := db.Model(&account).Update("password", string(hashedNewPassword)).Error; err != nil {
+ http.Error(w, errors.Wrap(err, "updating password").Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+}
+
+func (a *App) classicGetNotes(w http.ResponseWriter, r *http.Request) {
+ user, ok := r.Context().Value(helpers.KeyUser).(database.User)
+ if !ok {
+ handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
+ return
+ }
+
+ var notes []database.Note
+ db := database.DBConn
+ if err := db.Where("user_id = ? AND encrypted = true", user.ID).Find(¬es).Error; err != nil {
+ handleError(w, "finding notes", err, http.StatusInternalServerError)
+ return
+ }
+
+ presented := presenters.PresentNotes(notes)
+ respondJSON(w, presented)
+}
diff --git a/pkg/server/api/handlers/classic_test.go b/pkg/server/api/handlers/classic_test.go
new file mode 100644
index 00000000..b3dd147a
--- /dev/null
+++ b/pkg/server/api/handlers/classic_test.go
@@ -0,0 +1,227 @@
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/mailer"
+ "github.com/dnote/dnote/pkg/server/testutils"
+ "github.com/pkg/errors"
+)
+
+func init() {
+ testutils.InitTestDB()
+
+ templatePath := fmt.Sprintf("%s/mailer/templates/src", testutils.ServerPath)
+ mailer.InitTemplates(&templatePath)
+}
+
+func TestClassicPresignin(t *testing.T) {
+ db := database.DBConn
+ defer testutils.ClearData()
+
+ alice := database.Account{
+ Email: database.ToNullString("alice@example.com"),
+ ClientKDFIteration: 100000,
+ }
+ bob := database.Account{
+ Email: database.ToNullString("bob@example.com"),
+ ClientKDFIteration: 200000,
+ }
+ testutils.MustExec(t, db.Save(&alice), "saving alice")
+ testutils.MustExec(t, db.Save(&bob), "saving bob")
+
+ testCases := []struct {
+ email string
+ expectedIteration int
+ }{
+ {
+ email: "alice@example.com",
+ expectedIteration: 100000,
+ },
+ {
+ email: "bob@example.com",
+ expectedIteration: 200000,
+ },
+ {
+ email: "chuck@example.com",
+ // If user does not exist, reply with a generic response
+ expectedIteration: 100000,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("presignin %s", tc.email), func(t *testing.T) {
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ endpoint := fmt.Sprintf("/classic/presignin?email=%s", tc.email)
+ req := testutils.MakeReq(server, "GET", endpoint, "")
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var got PresigninResponse
+ if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ assert.Equal(t, got.Iteration, tc.expectedIteration, "Iteration mismatch")
+ })
+ }
+}
+
+func TestClassicPresignin_MissingParams(t *testing.T) {
+ defer testutils.ClearData()
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ req := testutils.MakeReq(server, "GET", "/classic/presignin", "")
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status mismatch")
+}
+
+func TestClassicSignin(t *testing.T) {
+ db := database.DBConn
+ defer testutils.ClearData()
+
+ user := testutils.SetupUserData()
+ alice := testutils.SetupClassicAccountData(user, "alice@example.com")
+ testutils.MustExec(t, db.Save(&alice), "saving alice")
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ dat := fmt.Sprintf(`{"email": "%s", "auth_key": "%s"}`, "alice@example.com", "/XCYisXJ6/o+vf6NUEtmrdYzJYPz+T9oAUCtMpOjhzc=")
+ req := testutils.MakeReq(server, "POST", "/classic/signin", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status mismatch")
+
+ var sessionCount int
+ var session database.Session
+ testutils.MustExec(t, db.Model(&database.Session{}).Count(&sessionCount), "counting session")
+ testutils.MustExec(t, db.First(&session), "getting session")
+
+ var got SessionResponse
+ if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ assert.Equal(t, sessionCount, 1, "sessionCount mismatch")
+ assert.Equal(t, got.Key, session.Key, "session Key mismatch")
+ assert.Equal(t, got.ExpiresAt, session.ExpiresAt.Unix(), "session ExpiresAt mismatch")
+
+ c := testutils.GetCookieByName(res.Cookies(), "id")
+ assert.Equal(t, c.Value, session.Key, "session key mismatch")
+ assert.Equal(t, c.Path, "/", "session path mismatch")
+ assert.Equal(t, c.HttpOnly, true, "session HTTPOnly mismatch")
+ assert.Equal(t, c.Expires.Unix(), session.ExpiresAt.Unix(), "session Expires mismatch")
+}
+
+func TestClassicSignin_Failure(t *testing.T) {
+ db := database.DBConn
+ defer testutils.ClearData()
+
+ //password: correctbattery
+ alice := database.Account{
+ Email: database.ToNullString("alice@example.com"),
+ ClientKDFIteration: 10000,
+ Salt: "Vw57HhZTqeOo0hWGb+BLoQ==",
+ // plain authKey: bKAcSKkGB4VrIaSckpZvHFIlqT6L+XMVY0CTsV2y5B8=
+ AuthKeyHash: "jjSs8JCaYi6cRGFPYNQ7XAVwKSrNpF1I1bGye62+A5U=",
+ }
+ bob := database.Account{
+ Email: database.ToNullString("bob@example.com"),
+ ClientKDFIteration: 10000,
+ Salt: "gShZ7X2AuYW1xZDkpavE3g==",
+ // plain authKey: DN4d/teaq1I2bVYZ7QWaah4Fu7q2y2N4yJNZk76hFHw=
+ AuthKeyHash: "fGOMHHAw9G7CH4Gv2EM1ZcZZklC1a55fS3QJ0qQVp4k=",
+ }
+ testutils.MustExec(t, db.Save(&alice), "saving alice")
+ testutils.MustExec(t, db.Save(&bob), "saving bob")
+
+ testCases := []struct {
+ email string
+ authKey string
+ }{
+ // missing params
+ {
+ email: "",
+ authKey: "",
+ },
+ {
+ email: "",
+ authKey: "GFSymYG+s64TyHSPD3TxxMLlBBurswhDWOZRmefSoGo=",
+ },
+ {
+ email: "alice@example.com",
+ authKey: "",
+ },
+ // send incorrect authKey
+ {
+ email: "alice@example.com",
+ authKey: "GFSymYG+s64TyHSPD3TxxMLlBBurswhDWOZRmefSoGo=",
+ },
+ {
+ email: "alice@example.com",
+ authKey: "D8b70qEl4CXlp2DqPQpjkLrxHfYZvrwHVA6W9wTDZ6E=",
+ },
+ // login with mixed credentials
+ {
+ email: "alice@example.com",
+ authKey: "DN4d/teaq1I2bVYZ7QWaah4Fu7q2y2N4yJNZk76hFHw=",
+ },
+ {
+ email: "bob@example.com",
+ authKey: "bKAcSKkGB4VrIaSckpZvHFIlqT6L+XMVY0CTsV2y5B8=",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("signin %s %s", tc.email, tc.authKey), func(t *testing.T) {
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ dat := fmt.Sprintf(`{"email": "%s", "auth_key": "%s"}`, tc.email, tc.authKey)
+ req := testutils.MakeReq(server, "POST", "/classic/signin", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
+ })
+ }
+}
diff --git a/pkg/server/api/handlers/digests.go b/pkg/server/api/handlers/digests.go
deleted file mode 100644
index 3f9a002d..00000000
--- a/pkg/server/api/handlers/digests.go
+++ /dev/null
@@ -1,149 +0,0 @@
-/* Copyright (C) 2019 Monomax Software Pty Ltd
- *
- * This file is part of Dnote.
- *
- * Dnote is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Dnote is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Dnote. If not, see .
- */
-
-package handlers
-
-import (
- "net/http"
- "strconv"
-
- "github.com/dnote/dnote/pkg/server/api/helpers"
- "github.com/dnote/dnote/pkg/server/api/presenters"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/gorilla/mux"
- "github.com/pkg/errors"
-)
-
-func respondWithDigest(w http.ResponseWriter, userID int, digestUUID string) {
- db := database.DBConn
-
- var digest database.Digest
- conn := db.Preload("Notes.Book").Where("user_id = ? AND uuid = ? ", userID, digestUUID).First(&digest)
- if conn.RecordNotFound() {
- handleError(w, "digest not found", nil, http.StatusNotFound)
- return
- } else if err := conn.Error; err != nil {
-
- handleError(w, "finding digest", err, http.StatusInternalServerError)
- return
- }
-
- presented := presenters.PresentDigest(digest)
- respondJSON(w, presented)
-}
-
-func (a App) getDigest(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- vars := mux.Vars(r)
- digestUUID := vars["digestUUID"]
-
- respondWithDigest(w, user.ID, digestUUID)
-}
-
-func (a App) getDemoDigest(w http.ResponseWriter, r *http.Request) {
- userID, err := helpers.GetDemoUserID()
- if err != nil {
- handleError(w, "finding demo user", err, http.StatusInternalServerError)
- return
- }
-
- vars := mux.Vars(r)
- digestUUID := vars["digestUUID"]
-
- respondWithDigest(w, userID, digestUUID)
-}
-
-func parseGetDigestsParams(r *http.Request) (int, error) {
- var page int
- var err error
-
- q := r.URL.Query()
- pageStr := q.Get("page")
-
- if pageStr != "" {
- page, err = strconv.Atoi(pageStr)
- if err != nil {
- return 0, errors.Wrap(err, "parsing page")
- }
-
- }
-
- return page, nil
-}
-
-// DigestsResponse is a response for getting digests
-type DigestsResponse struct {
- Total int `json:"total"`
- Digests []presenters.Digest `json:"digests"`
-}
-
-func respondWithDigests(w http.ResponseWriter, r *http.Request, userID int) {
- db := database.DBConn
-
- page, err := parseGetDigestsParams(r)
- if err != nil {
- handleError(w, "parsing params", err, http.StatusBadRequest)
- return
- }
- perPage := 25
- offset := (page - 1) * perPage
-
- var digests []database.Digest
- conn := db.Where("user_id = ?", userID).Order("created_at DESC").Offset(offset).Limit(perPage)
- if err := conn.Find(&digests).Error; err != nil {
- handleError(w, "finding digests", err, http.StatusInternalServerError)
- return
- }
-
- var total int
- if err := db.Model(database.Digest{}).Where("user_id = ?", userID).Count(&total).Error; err != nil {
- handleError(w, "counting digests", err, http.StatusInternalServerError)
- return
- }
-
- res := DigestsResponse{
- Total: total,
- Digests: presenters.PresentDigests(digests),
- }
- respondJSON(w, res)
-}
-
-func (a *App) getDigests(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- respondWithDigests(w, r, user.ID)
-}
-
-func (a *App) getDemoDigests(w http.ResponseWriter, r *http.Request) {
- userID, err := helpers.GetDemoUserID()
- if err != nil {
- handleError(w, "finding demo user", err, http.StatusInternalServerError)
- return
- }
-
- respondWithDigests(w, r, userID)
-}
diff --git a/pkg/server/api/handlers/health_test.go b/pkg/server/api/handlers/health_test.go
new file mode 100644
index 00000000..50480bb7
--- /dev/null
+++ b/pkg/server/api/handlers/health_test.go
@@ -0,0 +1,28 @@
+package handlers
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/testutils"
+)
+
+func TestCheckHealth(t *testing.T) {
+ defer testutils.ClearData()
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ // Execute
+ req := testutils.MakeReq(server, "GET", "/health", "")
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismtach")
+}
diff --git a/pkg/server/api/handlers/helpers.go b/pkg/server/api/handlers/helpers.go
index 62afd60b..429b56a9 100644
--- a/pkg/server/api/handlers/helpers.go
+++ b/pkg/server/api/handlers/helpers.go
@@ -132,3 +132,9 @@ func respondJSON(w http.ResponseWriter, payload interface{}) {
handleError(w, "encoding response", err, http.StatusInternalServerError)
}
}
+
+// notSupported is the handler for the route that is no longer supported
+func (a *App) notSupported(w http.ResponseWriter, r *http.Request) {
+ http.Error(w, "API version is not supported. Please upgrade your client.", http.StatusGone)
+ return
+}
diff --git a/pkg/server/api/handlers/notes.go b/pkg/server/api/handlers/notes.go
index e5b9fcb0..1adc2edb 100644
--- a/pkg/server/api/handlers/notes.go
+++ b/pkg/server/api/handlers/notes.go
@@ -19,6 +19,7 @@
package handlers
import (
+ "fmt"
"net/http"
"net/url"
"strconv"
@@ -33,41 +34,70 @@ import (
"github.com/pkg/errors"
)
+type ftsParams struct {
+ HighlightAll bool
+}
+
+func getHeadlineOptions(params *ftsParams) string {
+ headlineOptions := []string{
+ "StartSel=",
+ "StopSel=",
+ "ShortWord=0",
+ }
+
+ if params != nil && params.HighlightAll {
+ headlineOptions = append(headlineOptions, "HighlightAll=true")
+ } else {
+ headlineOptions = append(headlineOptions, "MaxFragments=3, MaxWords=50, MinWords=10")
+ }
+
+ return strings.Join(headlineOptions, ",")
+}
+
+func selectFTSFields(conn *gorm.DB, search string, params *ftsParams) *gorm.DB {
+ headlineOpts := getHeadlineOptions(params)
+
+ return conn.Select(`
+notes.id,
+notes.uuid,
+notes.created_at,
+notes.updated_at,
+notes.book_uuid,
+notes.user_id,
+notes.added_on,
+notes.edited_on,
+notes.usn,
+notes.deleted,
+notes.encrypted,
+ts_headline('english_nostop', notes.body, plainto_tsquery('english_nostop', ?), ?) AS body
+ `, search, headlineOpts)
+}
+
func respondWithNote(w http.ResponseWriter, note database.Note) {
presentedNote := presenters.PresentNote(note)
respondJSON(w, presentedNote)
}
-func preloadNote(conn *gorm.DB) *gorm.DB {
- return conn.Preload("Book").Preload("User")
+func parseSearchQuery(q url.Values) string {
+ searchStr := q.Get("q")
+
+ return escapeSearchQuery(searchStr)
}
-func (a *App) getDemoNote(w http.ResponseWriter, r *http.Request) {
+func getNoteBaseQuery(noteUUID string, userID int, search string) *gorm.DB {
db := database.DBConn
- vars := mux.Vars(r)
- noteUUID := vars["noteUUID"]
- demoUserID, err := helpers.GetDemoUserID()
- if err != nil {
- handleError(w, "finding demo user", err, http.StatusInternalServerError)
- return
+ var conn *gorm.DB
+ if search != "" {
+ conn = selectFTSFields(db, search, &ftsParams{HighlightAll: true})
+ } else {
+ conn = db
}
- var note database.Note
- conn := db.Where("uuid = ? AND user_id = ?", noteUUID, demoUserID)
- conn = preloadNote(conn)
- conn.Find(¬e)
+ conn = conn.Where("notes.uuid = ? AND notes.user_id = ?", noteUUID, userID)
- if conn.RecordNotFound() {
- handleError(w, "not found", nil, http.StatusNotFound)
- return
- } else if err := conn.Error; err != nil {
- handleError(w, "finding note", err, http.StatusInternalServerError)
- return
- }
-
- respondWithNote(w, note)
+ return conn
}
func (a *App) getNote(w http.ResponseWriter, r *http.Request) {
@@ -77,17 +107,17 @@ func (a *App) getNote(w http.ResponseWriter, r *http.Request) {
return
}
- db := database.DBConn
vars := mux.Vars(r)
noteUUID := vars["noteUUID"]
+ search := parseSearchQuery(r.URL.Query())
var note database.Note
- conn := db.Where("uuid = ? AND user_id = ?", noteUUID, user.ID)
+ conn := getNoteBaseQuery(noteUUID, user.ID, search)
conn = preloadNote(conn)
conn.Find(¬e)
if conn.RecordNotFound() {
- handleError(w, "not found", nil, http.StatusNotFound)
+ http.Error(w, "not found", http.StatusNotFound)
return
} else if err := conn.Error; err != nil {
handleError(w, "finding note", err, http.StatusInternalServerError)
@@ -101,20 +131,13 @@ func (a *App) getNote(w http.ResponseWriter, r *http.Request) {
// GetNotesResponse is a reponse by getNotesHandler
type GetNotesResponse struct {
- Notes []presenters.Note `json:"notes"`
- Total int `json:"total"`
- PrevDate *int64 `json:"prev_date"`
+ Notes []presenters.Note `json:"notes"`
+ Total int `json:"total"`
}
-func (a *App) getDemoNotes(w http.ResponseWriter, r *http.Request) {
- userID, err := helpers.GetDemoUserID()
- if err != nil {
- handleError(w, "finding demo user id", err, http.StatusInternalServerError)
- return
- }
- query := r.URL.Query()
-
- respondGetNotes(userID, query, w)
+type dateRange struct {
+ lower int64
+ upper int64
}
func (a *App) getNotes(w http.ResponseWriter, r *http.Request) {
@@ -129,26 +152,16 @@ func (a *App) getNotes(w http.ResponseWriter, r *http.Request) {
}
func respondGetNotes(userID int, query url.Values, w http.ResponseWriter) {
- err := validateGetNotesQuery(query)
- if err != nil {
- handleError(w, "validating query parameters", err, http.StatusBadRequest)
- return
- }
-
q, err := parseGetNotesQuery(query)
if err != nil {
- handleError(w, "parsing query parameters", err, http.StatusBadRequest)
+ http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- dateLowerbound, dateUpperbound := getDateBounds(q.Year, q.Month)
-
- baseConn := getNotesBaseQuery(userID, q)
- conn := baseConn.Where("notes.added_on >= ? AND notes.added_on < ?", dateLowerbound, dateUpperbound)
+ conn := getNotesBaseQuery(userID, q)
var total int
- err = conn.Model(database.Note{}).Count(&total).Error
- if err != nil {
+ if err := conn.Model(database.Note{}).Count(&total).Error; err != nil {
handleError(w, "counting total", err, http.StatusInternalServerError)
return
}
@@ -159,88 +172,45 @@ func respondGetNotes(userID int, query url.Values, w http.ResponseWriter) {
conn = preloadNote(conn)
conn = paginate(conn, q.Page)
- err = conn.Find(¬es).Error
- if err != nil {
+ if err := conn.Find(¬es).Error; err != nil {
handleError(w, "finding notes", err, http.StatusInternalServerError)
return
}
}
- // peek the prev date
- var prevDateUpperbound int64
- if len(notes) > 0 {
- lastNote := notes[len(notes)-1]
- prevDateUpperbound = lastNote.AddedOn
- } else {
- prevDateUpperbound = dateLowerbound
- }
-
- prevDate, err := getPrevDate(baseConn, prevDateUpperbound)
- if err != nil {
- handleError(w, "getting prevDate", err, http.StatusInternalServerError)
- return
- }
-
- presentedNotes := presenters.PresentNotes(notes)
-
response := GetNotesResponse{
- Notes: presentedNotes,
- Total: total,
- PrevDate: prevDate,
+ Notes: presenters.PresentNotes(notes),
+ Total: total,
}
respondJSON(w, response)
}
-func getPrevDate(baseConn *gorm.DB, dateUpperbound int64) (*int64, error) {
- var prevNote database.Note
-
- conn := baseConn.
- Select("notes.added_on").
- Where("notes.added_on < ?", dateUpperbound).
- Order("notes.added_on DESC")
-
- if conn.First(&prevNote).RecordNotFound() {
- return nil, nil
- }
-
- if err := conn.Error; err != nil {
- return nil, errors.Wrap(err, "querying previous note")
- }
-
- return &prevNote.AddedOn, nil
-}
-
-func validateGetNotesQuery(q url.Values) error {
- if q.Get("year") == "" {
- return errors.New("'year' is required")
- }
- if q.Get("month") == "" {
- return errors.New("'month' is required")
- }
-
- return nil
-}
-
type getNotesQuery struct {
Year int
Month int
Page int
- BookUUID string
- Encrypted *bool
+ Books []string
+ Search string
+ Encrypted bool
}
func parseGetNotesQuery(q url.Values) (getNotesQuery, error) {
yearStr := q.Get("year")
monthStr := q.Get("month")
- bookStr := q.Get("book")
+ books := q["book"]
pageStr := q.Get("page")
encryptedStr := q.Get("encrypted")
+ fmt.Println("books", books)
+
var page int
if len(pageStr) > 0 {
p, err := strconv.Atoi(pageStr)
if err != nil {
- return getNotesQuery{}, errors.Wrap(err, "parsing page")
+ return getNotesQuery{}, errors.Errorf("invalid page %s", pageStr)
+ }
+ if p < 1 {
+ return getNotesQuery{}, errors.Errorf("invalid page %s", pageStr)
}
page = p
@@ -248,30 +218,42 @@ func parseGetNotesQuery(q url.Values) (getNotesQuery, error) {
page = 1
}
- year, err := strconv.Atoi(yearStr)
- if err != nil {
- return getNotesQuery{}, errors.Wrapf(err, "invalid year %s", yearStr)
- }
- month, err := strconv.Atoi(monthStr)
- if err != nil {
- return getNotesQuery{}, errors.Wrapf(err, "invalid month %s", monthStr)
- }
- if month < 1 || month > 12 {
- return getNotesQuery{}, errors.Errorf("Invalid month %s", monthStr)
+ var year int
+ if len(yearStr) > 0 {
+ y, err := strconv.Atoi(yearStr)
+ if err != nil {
+ return getNotesQuery{}, errors.Errorf("invalid year %s", yearStr)
+ }
+
+ year = y
}
- var encrypted *bool
+ var month int
+ if len(monthStr) > 0 {
+ m, err := strconv.Atoi(monthStr)
+ if err != nil {
+ return getNotesQuery{}, errors.Errorf("invalid month %s", monthStr)
+ }
+ if m < 1 || m > 12 {
+ return getNotesQuery{}, errors.Errorf("invalid month %s", monthStr)
+ }
+
+ month = m
+ }
+
+ var encrypted bool
if strings.ToLower(encryptedStr) == "true" {
- *encrypted = true
- } else if strings.ToLower(encryptedStr) == "false" {
- *encrypted = false
+ encrypted = true
+ } else {
+ encrypted = false
}
ret := getNotesQuery{
Year: year,
Month: month,
Page: page,
- BookUUID: bookStr,
+ Search: parseSearchQuery(q),
+ Books: books,
Encrypted: encrypted,
}
@@ -298,14 +280,24 @@ func getDateBounds(year, month int) (int64, int64) {
func getNotesBaseQuery(userID int, q getNotesQuery) *gorm.DB {
db := database.DBConn
- conn := db.Where("notes.user_id = ? AND notes.deleted = ?", userID, false)
+ conn := db.Debug().Where(
+ "notes.user_id = ? AND notes.deleted = ? AND notes.encrypted = ?",
+ userID, false, q.Encrypted,
+ )
- if len(q.BookUUID) > 0 {
- conn = conn.Joins("INNER JOIN books ON books.uuid = notes.book_uuid").
- Where("books.uuid = ?", q.BookUUID)
+ if q.Search != "" {
+ conn = selectFTSFields(conn, q.Search, nil)
+ conn = conn.Where("tsv @@ plainto_tsquery('english_nostop', ?)", q.Search)
}
- if q.Encrypted != nil {
- conn = conn.Where("notes.encrypted = ?", *q.Encrypted)
+
+ if len(q.Books) > 0 {
+ conn = conn.Joins("INNER JOIN books ON books.uuid = notes.book_uuid").
+ Where("books.label in (?)", q.Books)
+ }
+
+ if q.Year != 0 || q.Month != 0 {
+ dateLowerbound, dateUpperbound := getDateBounds(q.Year, q.Month)
+ conn = conn.Where("notes.added_on >= ? AND notes.added_on < ?", dateLowerbound, dateUpperbound)
}
return conn
@@ -315,6 +307,15 @@ func orderGetNotes(conn *gorm.DB) *gorm.DB {
return conn.Order("notes.added_on DESC, notes.id DESC")
}
+func preloadNote(conn *gorm.DB) *gorm.DB {
+ return conn.Preload("Book").Preload("User")
+}
+
+// escapeSearchQuery escapes the query for full text search
+func escapeSearchQuery(searchQuery string) string {
+ return strings.Join(strings.Fields(searchQuery), "&")
+}
+
func (a *App) legacyGetNotes(w http.ResponseWriter, r *http.Request) {
user, ok := r.Context().Value(helpers.KeyUser).(database.User)
if !ok {
@@ -324,7 +325,7 @@ func (a *App) legacyGetNotes(w http.ResponseWriter, r *http.Request) {
var notes []database.Note
db := database.DBConn
- if err := db.Where("user_id = ? AND encrypted = false", user.ID).Find(¬es).Error; err != nil {
+ if err := db.Where("user_id = ? AND encrypted = true", user.ID).Find(¬es).Error; err != nil {
handleError(w, "finding notes", err, http.StatusInternalServerError)
return
}
diff --git a/pkg/server/api/handlers/notes_test.go b/pkg/server/api/handlers/notes_test.go
new file mode 100644
index 00000000..29b076f7
--- /dev/null
+++ b/pkg/server/api/handlers/notes_test.go
@@ -0,0 +1,324 @@
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/api/presenters"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/testutils"
+ "github.com/pkg/errors"
+)
+
+func init() {
+ testutils.InitTestDB()
+}
+
+func TestGetNotes(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ anotherUser := testutils.SetupUserData()
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, db.Save(&b2), "preparing b2")
+ b3 := database.Book{
+ UserID: anotherUser.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, db.Save(&b3), "preparing b3")
+
+ n1 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n1 content",
+ USN: 11,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, db.Save(&n1), "preparing n1")
+ n2 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n2 content",
+ USN: 14,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.August, 11, 22, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, db.Save(&n2), "preparing n2")
+ n3 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n3 content",
+ USN: 17,
+ Deleted: false,
+ AddedOn: time.Date(2017, time.January, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, db.Save(&n3), "preparing n3")
+ n4 := database.Note{
+ UserID: user.ID,
+ BookUUID: b2.UUID,
+ Body: "n4 content",
+ USN: 18,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.September, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, db.Save(&n4), "preparing n4")
+ n5 := database.Note{
+ UserID: anotherUser.ID,
+ BookUUID: b3.UUID,
+ Body: "n5 content",
+ USN: 19,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, db.Save(&n5), "preparing n5")
+ n6 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "",
+ USN: 11,
+ Deleted: true,
+ AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, db.Save(&n6), "preparing n6")
+
+ // Execute
+ req := testutils.MakeReq(server, "GET", "/notes?year=2018&month=8", "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var payload GetNotesResponse
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var n2Record, n1Record database.Note
+ testutils.MustExec(t, db.Where("uuid = ?", n2.UUID).First(&n2Record), "finding n2Record")
+ testutils.MustExec(t, db.Where("uuid = ?", n1.UUID).First(&n1Record), "finding n1Record")
+
+ expected := GetNotesResponse{
+ Notes: []presenters.Note{
+ {
+ CreatedAt: n2Record.CreatedAt,
+ UpdatedAt: n2Record.UpdatedAt,
+ UUID: n2Record.UUID,
+ Body: n2Record.Body,
+ AddedOn: n2Record.AddedOn,
+ USN: n2Record.USN,
+ Book: presenters.NoteBook{
+ UUID: b1.UUID,
+ Label: b1.Label,
+ },
+ User: presenters.NoteUser{
+ Name: user.Name,
+ UUID: user.UUID,
+ },
+ },
+ {
+ CreatedAt: n1Record.CreatedAt,
+ UpdatedAt: n1Record.UpdatedAt,
+ UUID: n1Record.UUID,
+ Body: n1Record.Body,
+ AddedOn: n1Record.AddedOn,
+ USN: n1Record.USN,
+ Book: presenters.NoteBook{
+ UUID: b1.UUID,
+ Label: b1.Label,
+ },
+ User: presenters.NoteUser{
+ Name: user.Name,
+ UUID: user.UUID,
+ },
+ },
+ },
+ Total: 2,
+ }
+
+ if ok := reflect.DeepEqual(payload, expected); !ok {
+ t.Errorf("Payload does not match.\nActual: %+v\nExpected: %+v", payload, expected)
+ }
+}
+
+func TestGetNote(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, db.Save(&b2), "preparing b2")
+
+ n1 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n1 content",
+ USN: 1123,
+ AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ Public: true,
+ }
+ testutils.MustExec(t, db.Save(&n1), "preparing n1")
+ n2 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n2 content",
+ USN: 1888,
+ AddedOn: time.Date(2018, time.August, 11, 22, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, db.Save(&n2), "preparing n2")
+
+ // Execute
+ url := fmt.Sprintf("/notes/%s", n1.UUID)
+ req := testutils.MakeReq(server, "GET", url, "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var payload presenters.Note
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var n1Record database.Note
+ testutils.MustExec(t, db.Where("uuid = ?", n1.UUID).First(&n1Record), "finding n1Record")
+
+ expected := presenters.Note{
+ UUID: n1Record.UUID,
+ CreatedAt: n1Record.CreatedAt,
+ UpdatedAt: n1Record.UpdatedAt,
+ Body: n1Record.Body,
+ AddedOn: n1Record.AddedOn,
+ Public: n1Record.Public,
+ USN: n1Record.USN,
+ Book: presenters.NoteBook{
+ UUID: b1.UUID,
+ Label: b1.Label,
+ },
+ User: presenters.NoteUser{
+ Name: user.Name,
+ UUID: user.UUID,
+ },
+ }
+
+ if ok := reflect.DeepEqual(payload, expected); !ok {
+ t.Errorf("Payload does not match.\nActual: %+v\nExpected: %+v", payload, expected)
+ }
+}
+
+// TODO: finish the test after implementing note sharing
+// func TestGetNote_guestAccessPrivate(t *testing.T) {
+// defer testutils.ClearData()
+// db := database.DBConn
+//
+// // Setup
+// server := httptest.NewServer(NewRouter(&App{
+// Clock: clock.NewMock(),
+// }))
+// defer server.Close()
+//
+// user := testutils.SetupUserData()
+//
+// b1 := database.Book{
+// UUID: "b1-uuid",
+// UserID: user.ID,
+// Label: "js",
+// }
+// testutils.MustExec(t, db.Save(&b1), "preparing b1")
+//
+// n1 := database.Note{
+// UserID: user.ID,
+// BookUUID: b1.UUID,
+// Body: "n1 content",
+// AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+// Public: false,
+// }
+// testutils.MustExec(t, db.Save(&n1), "preparing n1")
+//
+// // Execute
+// url := fmt.Sprintf("/notes/%s", n1.UUID)
+// req := testutils.MakeReq(server, "GET", url, "")
+//
+// res := testutils.HTTPDo(t, req)
+//
+// // Test
+// assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
+// }
+
+// func TestGetNote_nonOwnerAccessPrivate(t *testing.T) {
+// defer testutils.ClearData()
+// db := database.DBConn
+//
+// // Setup
+// server := httptest.NewServer(NewRouter(&App{
+// Clock: clock.NewMock(),
+// }))
+// defer server.Close()
+//
+// owner := testutils.SetupUserData()
+//
+// nonOwner := testutils.SetupUserData()
+// testutils.MustExec(t, db.Model(&nonOwner).Update("api_key", "non-owner-api-key"), "preparing user max_usn")
+//
+// b1 := database.Book{
+// UUID: "b1-uuid",
+// UserID: owner.ID,
+// Label: "js",
+// }
+// testutils.MustExec(t, db.Save(&b1), "preparing b1")
+//
+// n1 := database.Note{
+// UserID: owner.ID,
+// BookUUID: b1.UUID,
+// Body: "n1 content",
+// AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+// Public: false,
+// }
+// testutils.MustExec(t, db.Save(&n1), "preparing n1")
+//
+// // Execute
+// url := fmt.Sprintf("/notes/%s", n1.UUID)
+// req := testutils.MakeReq(server, "GET", url, "")
+// res := testutils.HTTPAuthDo(t, req, nonOwner)
+//
+// // Test
+// assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
+// }
diff --git a/pkg/server/api/handlers/routes.go b/pkg/server/api/handlers/routes.go
index 44dabfe6..0086668b 100644
--- a/pkg/server/api/handlers/routes.go
+++ b/pkg/server/api/handlers/routes.go
@@ -31,7 +31,6 @@ import (
"github.com/dnote/dnote/pkg/server/database"
"github.com/dnote/dnote/pkg/server/log"
"github.com/gorilla/mux"
- "github.com/markbates/goth/gothic"
"github.com/pkg/errors"
"github.com/stripe/stripe-go"
)
@@ -294,8 +293,8 @@ func (w *logResponseWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
}
-func logging(inner http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+func logging(inner http.Handler) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
lw := logResponseWriter{w, http.StatusOK}
@@ -309,10 +308,10 @@ func logging(inner http.Handler) http.Handler {
"duration": fmt.Sprintf("%dms", time.Since(start)/1000000),
"userAgent": r.Header.Get("User-Agent"),
}).Info("incoming request")
- })
+ }
}
-func applyMiddleware(h http.Handler, rateLimit bool) http.Handler {
+func applyMiddleware(h http.HandlerFunc, rateLimit bool) http.Handler {
ret := h
ret = logging(ret)
@@ -350,10 +349,9 @@ func NewRouter(app *App) *mux.Router {
{"GET", "/me", auth(app.getMe, nil), true},
{"POST", "/verification-token", auth(app.createVerificationToken, nil), true},
{"PATCH", "/verify-email", app.verifyEmail, true},
- {"GET", "/auth/{provider}", gothic.BeginAuthHandler, true},
- {"GET", "/auth/{provider}/callback", app.oauthCallbackHandler, true},
+ {"POST", "/reset-token", app.createResetToken, true},
+ {"PATCH", "/reset-password", app.resetPassword, true},
{"PATCH", "/account/profile", auth(app.updateProfile, nil), true},
- {"PATCH", "/account/email", auth(app.updateEmail, nil), true},
{"PATCH", "/account/password", auth(app.updatePassword, nil), true},
{"GET", "/account/email-preference", tokenAuth(app.getEmailPreference, database.TokenTypeEmailPreference), true},
{"PATCH", "/account/email-preference", tokenAuth(app.updateEmailPreference, database.TokenTypeEmailPreference), true},
@@ -364,59 +362,41 @@ func NewRouter(app *App) *mux.Router {
{"GET", "/stripe_source", auth(app.getStripeSource, nil), true},
{"PATCH", "/stripe_source", auth(app.updateStripeSource, nil), true},
{"GET", "/notes", auth(app.getNotes, &proOnly), false},
- {"GET", "/demo/notes", app.getDemoNotes, true},
{"GET", "/notes/{noteUUID}", auth(app.getNote, &proOnly), true},
- {"GET", "/demo/notes/{noteUUID}", app.getDemoNote, true},
{"GET", "/calendar", auth(app.getCalendar, &proOnly), true},
- {"GET", "/demo/calendar", app.getDemoCalendar, true},
- {"GET", "/digests/{digestUUID}", auth(app.getDigest, &proOnly), true},
- {"GET", "/demo/digests/{digestUUID}", app.getDemoDigest, true},
- {"GET", "/digests", auth(app.getDigests, &proOnly), true},
- {"GET", "/demo/digests", app.getDemoDigests, true},
- //Route{"GET", "/books/{bookUUID}", cors(auth(app.getBook)), true},
- // routes for user migration to use encryption
- {"POST", "/legacy/signin", app.legacyPasswordLogin, true},
- {"POST", "/legacy/register", legacyAuth(app.legacyRegister), true},
- {"GET", "/legacy/me", legacyAuth(app.getMe), true},
- {"GET", "/legacy/notes", auth(app.legacyGetNotes, &proOnly), false},
- {"PATCH", "/legacy/migrate", auth(app.legacyMigrate, &proOnly), false},
- {"GET", "/auth/{provider}", gothic.BeginAuthHandler, true},
- {"GET", "/auth/{provider}/callback", app.oauthCallbackHandler, true},
+ // migration of classic users
+ {"GET", "/classic/presignin", cors(app.classicPresignin), true},
+ {"POST", "/classic/signin", cors(app.classicSignin), true},
+ {"PATCH", "/classic/migrate", auth(app.classicMigrate, &proOnly), true},
+ {"GET", "/classic/notes", auth(app.classicGetNotes, nil), true},
+ {"PATCH", "/classic/set-password", auth(app.classicSetPassword, nil), true},
- // v1
- {"POST", "/v1/sync", cors(app.Sync), true},
- {"GET", "/v1/sync/fragment", cors(auth(app.GetSyncFragment, &proOnly)), true},
- {"GET", "/v1/sync/state", cors(auth(app.GetSyncState, &proOnly)), true},
-
- {"OPTIONS", "/v1/books", cors(app.BooksOptions), false},
- {"GET", "/v1/demo/books", app.GetDemoBooks, true},
- {"GET", "/v1/books", cors(auth(app.GetBooks, &proOnly)), true},
- {"GET", "/v1/books/{bookUUID}", cors(auth(app.GetBook, &proOnly)), true},
- {"POST", "/v1/books", cors(app.CreateBook), false},
- {"PATCH", "/v1/books/{bookUUID}", cors(auth(app.UpdateBook, &proOnly)), false},
- {"DELETE", "/v1/books/{bookUUID}", cors(auth(app.DeleteBook, &proOnly)), false},
-
- {"OPTIONS", "/v1/notes", cors(app.NotesOptions), true},
- {"POST", "/v1/notes", cors(app.CreateNote), false},
- {"PATCH", "/v1/notes/{noteUUID}", auth(app.UpdateNote, &proOnly), false},
- {"DELETE", "/v1/notes/{noteUUID}", auth(app.DeleteNote, &proOnly), false},
-
- {"POST", "/v1/register", app.register, true},
- {"GET", "/v1/presignin", cors(app.presignin), true},
- {"POST", "/v1/signin", cors(app.signin), true},
- {"OPTIONS", "/v1/signout", cors(app.signoutOptions), true},
- {"POST", "/v1/signout", cors(app.signout), true},
-
- // v2
- {"OPTIONS", "/v2/notes", cors(app.NotesOptionsV2), true},
- {"POST", "/v2/notes", cors(auth(app.CreateNoteV2, &proOnly)), true},
-
- {"OPTIONS", "/v2/books", cors(app.BooksOptionsV2), true},
- {"POST", "/v2/books", cors(auth(app.CreateBookV2, &proOnly)), true},
+ // v3
+ {"GET", "/v3/sync/fragment", cors(auth(app.GetSyncFragment, &proOnly)), true},
+ {"GET", "/v3/sync/state", cors(auth(app.GetSyncState, &proOnly)), true},
+ {"OPTIONS", "/v3/books", cors(app.BooksOptions), true},
+ {"GET", "/v3/books", cors(auth(app.GetBooks, &proOnly)), true},
+ {"GET", "/v3/books/{bookUUID}", cors(auth(app.GetBook, &proOnly)), true},
+ {"POST", "/v3/books", cors(auth(app.CreateBook, &proOnly)), true},
+ {"PATCH", "/v3/books/{bookUUID}", cors(auth(app.UpdateBook, &proOnly)), false},
+ {"DELETE", "/v3/books/{bookUUID}", cors(auth(app.DeleteBook, &proOnly)), false},
+ {"GET", "/v3/demo/books", app.GetDemoBooks, true},
+ {"OPTIONS", "/v3/notes", cors(app.NotesOptions), true},
+ {"POST", "/v3/notes", cors(auth(app.CreateNote, &proOnly)), true},
+ {"PATCH", "/v3/notes/{noteUUID}", auth(app.UpdateNote, &proOnly), false},
+ {"DELETE", "/v3/notes/{noteUUID}", auth(app.DeleteNote, &proOnly), false},
+ {"POST", "/v3/signin", cors(app.signin), true},
+ {"OPTIONS", "/v3/signout", cors(app.signoutOptions), true},
+ {"POST", "/v3/signout", cors(app.signout), true},
+ {"POST", "/v3/register", app.register, true},
}
router := mux.NewRouter().StrictSlash(true)
+
+ router.PathPrefix("/v1").Handler(applyMiddleware(app.notSupported, true))
+ router.PathPrefix("/v2").Handler(applyMiddleware(app.notSupported, true))
+
for _, route := range routes {
handler := route.HandlerFunc
diff --git a/pkg/server/api/handlers/routes_test.go b/pkg/server/api/handlers/routes_test.go
index 43ae67dc..73712ef3 100644
--- a/pkg/server/api/handlers/routes_test.go
+++ b/pkg/server/api/handlers/routes_test.go
@@ -26,6 +26,7 @@ import (
"time"
"github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
"github.com/dnote/dnote/pkg/server/database"
"github.com/dnote/dnote/pkg/server/testutils"
"github.com/pkg/errors"
@@ -184,6 +185,8 @@ func TestGetCredential(t *testing.T) {
}
func TestAuthMiddleware(t *testing.T) {
+ defer testutils.ClearData()
+
// set up
db := database.DBConn
@@ -297,6 +300,8 @@ func TestAuthMiddleware(t *testing.T) {
}
func TestTokenAuthMiddleWare(t *testing.T) {
+ defer testutils.ClearData()
+
// set up
db := database.DBConn
@@ -424,3 +429,47 @@ func TestTokenAuthMiddleWare(t *testing.T) {
assert.Equal(t, res.StatusCode, http.StatusUnauthorized, "status code mismatch")
})
}
+
+func TestNotSupportedVersions(t *testing.T) {
+ testCases := []struct {
+ path string
+ }{
+ // v1
+ {
+ path: "/v1",
+ },
+ {
+ path: "/v1/foo",
+ },
+ {
+ path: "/v1/bar/baz",
+ },
+ // v2
+ {
+ path: "/v2",
+ },
+ {
+ path: "/v2/foo",
+ },
+ {
+ path: "/v2/bar/baz",
+ },
+ }
+
+ // setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ for _, tc := range testCases {
+ t.Run(tc.path, func(t *testing.T) {
+ // execute
+ req := testutils.MakeReq(server, "GET", tc.path, "")
+ res := testutils.HTTPDo(t, req)
+
+ // test
+ assert.Equal(t, res.StatusCode, http.StatusGone, "status code mismatch")
+ })
+ }
+}
diff --git a/pkg/server/api/handlers/user.go b/pkg/server/api/handlers/user.go
index da973c1e..8d096130 100644
--- a/pkg/server/api/handlers/user.go
+++ b/pkg/server/api/handlers/user.go
@@ -23,16 +23,17 @@ import (
"net/http"
"time"
- "github.com/dnote/dnote/pkg/server/api/crypt"
"github.com/dnote/dnote/pkg/server/api/helpers"
- "github.com/dnote/dnote/pkg/server/api/operations"
+ "github.com/dnote/dnote/pkg/server/api/presenters"
"github.com/dnote/dnote/pkg/server/database"
"github.com/dnote/dnote/pkg/server/log"
"github.com/dnote/dnote/pkg/server/mailer"
+ "github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
)
type updateProfilePayload struct {
- Name string `json:"name"`
+ Email string `json:"email"`
}
// updateProfile updates user
@@ -48,68 +49,12 @@ func (a *App) updateProfile(w http.ResponseWriter, r *http.Request) {
var params updateProfilePayload
err := json.NewDecoder(r.Body).Decode(¶ms)
if err != nil {
- handleError(w, "decoding params", err, http.StatusInternalServerError)
+ http.Error(w, errors.Wrap(err, "invalid params").Error(), http.StatusBadRequest)
return
}
// Validate
- if len(params.Name) > 50 {
- http.Error(w, "Name is too long", http.StatusBadRequest)
- return
- }
-
- var account database.Account
- err = db.Where("user_id = ?", user.ID).First(&account).Error
- if err != nil {
- handleError(w, "finding account", err, http.StatusInternalServerError)
- return
- }
-
- tx := db.Begin()
- user.Name = params.Name
- if err := tx.Save(&user).Error; err != nil {
- tx.Rollback()
- handleError(w, "saving user", err, http.StatusInternalServerError)
- return
- }
-
- if err := tx.Save(&account).Error; err != nil {
- tx.Rollback()
- handleError(w, "saving user", err, http.StatusInternalServerError)
- return
- }
- tx.Commit()
-
- session := makeSession(user, account)
- respondJSON(w, session)
-}
-
-type updateEmailPayload struct {
- NewEmail string `json:"new_email"`
- NewCipherKeyEnc string `json:"new_cipher_key_enc"`
- OldAuthKey string `json:"old_auth_key"`
- NewAuthKey string `json:"new_auth_key"`
-}
-
-// updateEmail updates user
-func (a *App) updateEmail(w http.ResponseWriter, r *http.Request) {
- db := database.DBConn
-
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var params updateEmailPayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handleError(w, "decoding params", err, http.StatusInternalServerError)
- return
- }
-
- // Validate
- if len(params.NewEmail) > 100 {
+ if len(params.Email) > 60 {
http.Error(w, "Email is too long", http.StatusBadRequest)
return
}
@@ -121,34 +66,35 @@ func (a *App) updateEmail(w http.ResponseWriter, r *http.Request) {
return
}
- authKeyHash := crypt.HashAuthKey(params.OldAuthKey, account.Salt, account.ServerKDFIteration)
- if account.AuthKeyHash != authKeyHash {
- http.Error(w, "wrong password", http.StatusUnauthorized)
- return
- }
-
- if account.Email.String == params.NewEmail {
- http.Error(w, "New email is the same as the old", http.StatusBadRequest)
- return
- }
-
tx := db.Begin()
+ if err := tx.Save(&user).Error; err != nil {
+ tx.Rollback()
+ handleError(w, "saving user", err, http.StatusInternalServerError)
+ return
+ }
- account.Email = database.ToNullString(params.NewEmail)
- account.CipherKeyEnc = params.NewCipherKeyEnc
- account.AuthKeyHash = crypt.HashAuthKey(params.NewAuthKey, account.Salt, crypt.ServerKDFIteration)
- account.ServerKDFIteration = crypt.ServerKDFIteration
- account.EmailVerified = false
+ // check if email was changed
+ if params.Email != account.Email.String {
+ account.EmailVerified = false
+ }
+ account.Email.String = params.Email
if err := tx.Save(&account).Error; err != nil {
tx.Rollback()
handleError(w, "saving account", err, http.StatusInternalServerError)
return
}
+
tx.Commit()
- session := makeSession(user, account)
- respondJSON(w, session)
+ respondWithSession(w, user.ID, http.StatusOK)
+}
+
+type updateEmailPayload struct {
+ NewEmail string `json:"new_email"`
+ NewCipherKeyEnc string `json:"new_cipher_key_enc"`
+ OldAuthKey string `json:"old_auth_key"`
+ NewAuthKey string `json:"new_auth_key"`
}
func respondWithCalendar(w http.ResponseWriter, userID int) {
@@ -219,7 +165,7 @@ func (a *App) createVerificationToken(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Email already verified", http.StatusGone)
return
}
- if !account.Email.Valid {
+ if account.Email.String == "" {
http.Error(w, "Email not set", http.StatusUnprocessableEntity)
return
}
@@ -249,7 +195,7 @@ func (a *App) createVerificationToken(w http.ResponseWriter, r *http.Request) {
subject,
tokenValue,
}
- email := mailer.NewEmail("noreply@dnote.io", []string{account.Email.String}, subject)
+ email := mailer.NewEmail("noreply@getdnote.com", []string{account.Email.String}, subject)
if err := email.ParseTemplate(mailer.EmailTypeEmailVerification, data); err != nil {
handleError(w, "parsing template", err, http.StatusInternalServerError)
return
@@ -326,7 +272,6 @@ func (a *App) verifyEmail(w http.ResponseWriter, r *http.Request) {
}
session := makeSession(user, account)
- setAuthCookie(w, user)
respondJSON(w, session)
}
@@ -394,14 +339,13 @@ func (a *App) getEmailPreference(w http.ResponseWriter, r *http.Request) {
return
}
- respondJSON(w, pref)
+ presented := presenters.PresentEmailPreference(pref)
+ respondJSON(w, presented)
}
type updatePasswordPayload struct {
- OldAuthKey string `json:"old_auth_key"`
- NewAuthKey string `json:"new_auth_key"`
- NewCipherKeyEnc string `json:"new_cipher_key_enc"`
- NewKDFIteration int `json:"new_kdf_iteration"`
+ OldPassword string `json:"old_password"`
+ NewPassword string `json:"new_password"`
}
func (a *App) updatePassword(w http.ResponseWriter, r *http.Request) {
@@ -412,44 +356,47 @@ func (a *App) updatePassword(w http.ResponseWriter, r *http.Request) {
handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
return
}
- var account database.Account
- if err := db.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
- handleError(w, "getting account", err, http.StatusInternalServerError)
- return
- }
+
var params updatePasswordPayload
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
- handleError(w, "decoding params", err, http.StatusInternalServerError)
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ if params.OldPassword == "" || params.NewPassword == "" {
+ http.Error(w, "invalid params", http.StatusBadRequest)
return
}
- oldAuthKeyHash := crypt.HashAuthKey(params.OldAuthKey, account.Salt, account.ServerKDFIteration)
- if oldAuthKeyHash != account.AuthKeyHash {
- http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
+ var account database.Account
+ if err := db.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
+ handleError(w, "getting user", nil, http.StatusInternalServerError)
+ return
+ }
+
+ password := []byte(params.OldPassword)
+ if err := bcrypt.CompareHashAndPassword([]byte(account.Password.String), password); err != nil {
log.WithFields(log.Fields{
- "account_id": account.ID,
- }).Error("Existing password mismatch")
+ "user_id": user.ID,
+ }).Warn("invalid password update attempt")
+ http.Error(w, "Wrong password", http.StatusUnauthorized)
return
}
- newAuthKeyHash := crypt.HashAuthKey(params.NewAuthKey, account.Salt, account.ServerKDFIteration)
-
- if err := db.
- Model(&account).
- Updates(map[string]interface{}{
- "auth_key_hash": newAuthKeyHash,
- "client_kdf_iteration": params.NewKDFIteration,
- "server_kdf_iteration": account.ServerKDFIteration,
- "cipher_key_enc": params.NewCipherKeyEnc,
- }).Error; err != nil {
- handleError(w, "updating account", err, http.StatusInternalServerError)
+ if err := validatePassword(params.NewPassword); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- if err := operations.DeleteUserSessions(db, user.ID); err != nil {
- handleError(w, "deleting user sessions", err, http.StatusBadRequest)
+ hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(params.NewPassword), bcrypt.DefaultCost)
+ if err != nil {
+ http.Error(w, errors.Wrap(err, "hashing password").Error(), http.StatusInternalServerError)
return
}
- respondWithSession(w, user.ID, account.CipherKeyEnc)
+ if err := db.Model(&account).Update("password", string(hashedNewPassword)).Error; err != nil {
+ http.Error(w, errors.Wrap(err, "updating password").Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
}
diff --git a/pkg/server/api/handlers/user_test.go b/pkg/server/api/handlers/user_test.go
new file mode 100644
index 00000000..aaa19771
--- /dev/null
+++ b/pkg/server/api/handlers/user_test.go
@@ -0,0 +1,652 @@
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/api/presenters"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/mailer"
+ "github.com/dnote/dnote/pkg/server/testutils"
+ "github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
+)
+
+func init() {
+ testutils.InitTestDB()
+
+}
+
+func TestUpdatePassword(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@example.com", "oldpassword")
+
+ // Execute
+ dat := `{"old_password": "oldpassword", "new_password": "newpassword"}`
+ req := testutils.MakeReq(server, "PATCH", "/account/password", dat)
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismsatch")
+
+ var account database.Account
+ testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
+
+ passwordErr := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte("newpassword"))
+ assert.Equal(t, passwordErr, nil, "Password mismatch")
+ })
+
+ t.Run("old password mismatch", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword")
+
+ // Execute
+ dat := `{"old_password": "randompassword", "new_password": "newpassword"}`
+ req := testutils.MakeReq(server, "PATCH", "/account/password", dat)
+ res := testutils.HTTPAuthDo(t, req, u)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "Status code mismsatch")
+
+ var account database.Account
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&account), "finding account")
+ assert.Equal(t, a.Password.String, account.Password.String, "password should not have been updated")
+ })
+
+ t.Run("password too short", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword")
+
+ // Execute
+ dat := `{"old_password": "oldpassword", "new_password": "a"}`
+ req := testutils.MakeReq(server, "PATCH", "/account/password", dat)
+ res := testutils.HTTPAuthDo(t, req, u)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismsatch")
+
+ var account database.Account
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&account), "finding account")
+ assert.Equal(t, a.Password.String, account.Password.String, "password should not have been updated")
+ })
+}
+
+func TestCreateVerificationToken(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+
+ // TODO: send emails in the background using job queue to avoid coupling the
+ // handler itself to the mailer
+ templatePath := fmt.Sprintf("%s/mailer/templates/src", testutils.ServerPath)
+ mailer.InitTemplates(&templatePath)
+
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@example.com", "pass1234")
+
+ // Execute
+ req := testutils.MakeReq(server, "POST", "/verification-token", "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusCreated, "status code mismatch")
+
+ var account database.Account
+ var token database.Token
+ var tokenCount int
+ testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
+ testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
+
+ assert.Equal(t, account.EmailVerified, false, "email_verified should not have been updated")
+ assert.NotEqual(t, token.Value, "", "token Value mismatch")
+ assert.Equal(t, tokenCount, 1, "token count mismatch")
+ assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token UsedAt mismatch")
+ })
+
+ t.Run("already verified", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ a := testutils.SetupAccountData(user, "alice@example.com", "pass1234")
+ a.EmailVerified = true
+ testutils.MustExec(t, db.Save(&a), "preparing account")
+
+ // Execute
+ req := testutils.MakeReq(server, "POST", "/verification-token", "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusGone, "Status code mismatch")
+
+ var account database.Account
+ var tokenCount int
+ testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
+
+ assert.Equal(t, account.EmailVerified, true, "email_verified should not have been updated")
+ assert.Equal(t, tokenCount, 0, "token count mismatch")
+ })
+}
+
+func TestVerifyEmail(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@example.com", "pass1234")
+ tok := database.Token{
+ UserID: user.ID,
+ Type: database.TokenTypeEmailVerification,
+ Value: "someTokenValue",
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ dat := `{"token": "someTokenValue"}`
+ req := testutils.MakeReq(server, "PATCH", "/verify-email", dat)
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismatch")
+
+ var account database.Account
+ var token database.Token
+ var tokenCount int
+ testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
+ testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
+
+ assert.Equal(t, account.EmailVerified, true, "email_verified mismatch")
+ assert.NotEqual(t, token.Value, "", "token value should not have been updated")
+ assert.Equal(t, tokenCount, 1, "token count mismatch")
+ assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
+ })
+
+ t.Run("used token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@example.com", "pass1234")
+
+ usedAt := time.Now().Add(time.Hour * -11).UTC()
+ tok := database.Token{
+ UserID: user.ID,
+ Type: database.TokenTypeEmailVerification,
+ Value: "someTokenValue",
+ UsedAt: &usedAt,
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ dat := `{"token": "someTokenValue"}`
+ req := testutils.MakeReq(server, "PATCH", "/verify-email", dat)
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "")
+
+ var account database.Account
+ var token database.Token
+ var tokenCount int
+ testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
+ testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
+
+ assert.Equal(t, account.EmailVerified, false, "email_verified mismatch")
+ assert.NotEqual(t, token.UsedAt, nil, "token used_at mismatch")
+ assert.Equal(t, tokenCount, 1, "token count mismatch")
+ assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
+ })
+
+ t.Run("expired token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@example.com", "pass1234")
+
+ tok := database.Token{
+ UserID: user.ID,
+ Type: database.TokenTypeEmailVerification,
+ Value: "someTokenValue",
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+ testutils.MustExec(t, db.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-31)), "Failed to prepare token created_at")
+
+ dat := `{"token": "someTokenValue"}`
+ req := testutils.MakeReq(server, "PATCH", "/verify-email", dat)
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusGone, "")
+
+ var account database.Account
+ var token database.Token
+ var tokenCount int
+ testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
+ testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
+
+ assert.Equal(t, account.EmailVerified, false, "email_verified mismatch")
+ assert.Equal(t, tokenCount, 1, "token count mismatch")
+ assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token should have not been used")
+ })
+
+ t.Run("already verified", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ a := testutils.SetupAccountData(user, "alice@example.com", "oldpass1234")
+ a.EmailVerified = true
+ testutils.MustExec(t, db.Save(&a), "preparing account")
+
+ tok := database.Token{
+ UserID: user.ID,
+ Type: database.TokenTypeEmailVerification,
+ Value: "someTokenValue",
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ dat := `{"token": "someTokenValue"}`
+ req := testutils.MakeReq(server, "PATCH", "/verify-email", dat)
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusConflict, "")
+
+ var account database.Account
+ var token database.Token
+ var tokenCount int
+ testutils.MustExec(t, db.Where("user_id = ?", user.ID).First(&account), "finding account")
+ testutils.MustExec(t, db.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&tokenCount), "counting token")
+
+ assert.Equal(t, account.EmailVerified, true, "email_verified mismatch")
+ assert.Equal(t, tokenCount, 1, "token count mismatch")
+ assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token should have not been used")
+ })
+}
+
+func TestUpdateEmail(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ a := testutils.SetupAccountData(u, "alice@example.com", "pass1234")
+ a.EmailVerified = true
+ testutils.MustExec(t, db.Save(&a), "updating email_verified")
+
+ // Execute
+ dat := `{"email": "alice-new@example.com"}`
+ req := testutils.MakeReq(server, "PATCH", "/account/profile", dat)
+ res := testutils.HTTPAuthDo(t, req, u)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var user database.User
+ var account database.Account
+ testutils.MustExec(t, db.Where("id = ?", u.ID).First(&user), "finding user")
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&account), "finding account")
+
+ assert.Equal(t, account.Email.String, "alice-new@example.com", "email mismatch")
+ assert.Equal(t, account.EmailVerified, false, "EmailVerified mismatch")
+ })
+}
+
+func TestUpdateEmailPreference(t *testing.T) {
+ t.Run("with login", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupEmailPreferenceData(u, false)
+
+ // Execute
+ dat := `{"digest_weekly": true}`
+ req := testutils.MakeReq(server, "PATCH", "/account/email-preference", dat)
+ res := testutils.HTTPAuthDo(t, req, u)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var preference database.EmailPreference
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&preference), "finding account")
+ assert.Equal(t, preference.DigestWeekly, true, "preference mismatch")
+ })
+
+ t.Run("with an unused token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupEmailPreferenceData(u, false)
+ tok := database.Token{
+ UserID: u.ID,
+ Type: database.TokenTypeEmailPreference,
+ Value: "someTokenValue",
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ // Execute
+ dat := `{"digest_weekly": true}`
+ url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
+ req := testutils.MakeReq(server, "PATCH", url, dat)
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var preference database.EmailPreference
+ var preferenceCount int
+ var token database.Token
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&preference), "finding preference")
+ testutils.MustExec(t, db.Model(database.EmailPreference{}).Count(&preferenceCount), "counting preference")
+ testutils.MustExec(t, db.Where("id = ?", tok.ID).First(&token), "failed to find token")
+
+ assert.Equal(t, preferenceCount, 1, "preference count mismatch")
+ assert.Equal(t, preference.DigestWeekly, true, "email mismatch")
+ assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
+ })
+
+ t.Run("with nonexistent token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupEmailPreferenceData(u, true)
+ tok := database.Token{
+ UserID: u.ID,
+ Type: database.TokenTypeEmailPreference,
+ Value: "someTokenValue",
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ dat := `{"digest_weekly": false}`
+ url := fmt.Sprintf("/account/email-preference?token=%s", "someNonexistentToken")
+ req := testutils.MakeReq(server, "PATCH", url, dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
+
+ var preference database.EmailPreference
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&preference), "finding preference")
+ assert.Equal(t, preference.DigestWeekly, true, "email mismatch")
+ })
+
+ t.Run("with expired token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupEmailPreferenceData(u, true)
+
+ usedAt := time.Now().Add(-11 * time.Minute)
+ tok := database.Token{
+ UserID: u.ID,
+ Type: database.TokenTypeEmailPreference,
+ Value: "someTokenValue",
+ UsedAt: &usedAt,
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ // Execute
+ dat := `{"digest_weekly": false}`
+ url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
+ req := testutils.MakeReq(server, "PATCH", url, dat)
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
+
+ var preference database.EmailPreference
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&preference), "finding preference")
+ assert.Equal(t, preference.DigestWeekly, true, "email mismatch")
+ })
+
+ t.Run("with a used but unexpired token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupEmailPreferenceData(u, true)
+ usedAt := time.Now().Add(-9 * time.Minute)
+ tok := database.Token{
+ UserID: u.ID,
+ Type: database.TokenTypeEmailPreference,
+ Value: "someTokenValue",
+ UsedAt: &usedAt,
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ dat := `{"digest_weekly": false}`
+ url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
+ req := testutils.MakeReq(server, "PATCH", url, dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var preference database.EmailPreference
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&preference), "finding preference")
+ assert.Equal(t, preference.DigestWeekly, false, "DigestWeekly mismatch")
+ })
+
+ t.Run("no user and no token", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupEmailPreferenceData(u, true)
+
+ // Execute
+ dat := `{"digest_weekly": false}`
+ req := testutils.MakeReq(server, "PATCH", "/account/email-preference", dat)
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
+
+ var preference database.EmailPreference
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&preference), "finding preference")
+ assert.Equal(t, preference.DigestWeekly, true, "email mismatch")
+ })
+
+ t.Run("create a record if not exists", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ tok := database.Token{
+ UserID: u.ID,
+ Type: database.TokenTypeEmailPreference,
+ Value: "someTokenValue",
+ }
+ testutils.MustExec(t, db.Save(&tok), "preparing token")
+
+ // Execute
+ dat := `{"digest_weekly": false}`
+ url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
+ req := testutils.MakeReq(server, "PATCH", url, dat)
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var preferenceCount int
+ testutils.MustExec(t, db.Model(database.EmailPreference{}).Count(&preferenceCount), "counting preference")
+ assert.Equal(t, preferenceCount, 1, "preference count mismatch")
+
+ var preference database.EmailPreference
+ testutils.MustExec(t, db.Where("user_id = ?", u.ID).First(&preference), "finding preference")
+ assert.Equal(t, preference.DigestWeekly, false, "email mismatch")
+ })
+}
+
+func TestGetEmailPreference(t *testing.T) {
+ defer testutils.ClearData()
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ pref := testutils.SetupEmailPreferenceData(u, true)
+
+ // Execute
+ req := testutils.MakeReq(server, "GET", "/account/email-preference", "")
+ res := testutils.HTTPAuthDo(t, req, u)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var got presenters.EmailPreference
+ if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ expected := presenters.EmailPreference{
+ DigestWeekly: pref.DigestWeekly,
+ CreatedAt: presenters.FormatTS(pref.CreatedAt),
+ UpdatedAt: presenters.FormatTS(pref.UpdatedAt),
+ }
+ assert.DeepEqual(t, got, expected, "payload mismatch")
+}
diff --git a/pkg/server/api/handlers/v2_books.go b/pkg/server/api/handlers/v2_books.go
deleted file mode 100644
index 793a3bd2..00000000
--- a/pkg/server/api/handlers/v2_books.go
+++ /dev/null
@@ -1,98 +0,0 @@
-/* Copyright (C) 2019 Monomax Software Pty Ltd
- *
- * This file is part of Dnote.
- *
- * Dnote is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Dnote is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Dnote. If not, see .
- */
-
-package handlers
-
-import (
- "encoding/json"
- "net/http"
-
- "github.com/dnote/dnote/pkg/server/api/helpers"
- "github.com/dnote/dnote/pkg/server/api/operations"
- "github.com/dnote/dnote/pkg/server/api/presenters"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/pkg/errors"
-)
-
-type createBookV2Payload struct {
- Name string `json:"name"`
-}
-
-// CreateBookV2Resp is the response from create book api
-type CreateBookV2Resp struct {
- Book presenters.Book `json:"book"`
-}
-
-func validateCreateBookV2Payload(p createBookPayload) error {
- if p.Name == "" {
- return errors.New("name is required")
- }
-
- return nil
-}
-
-// CreateBookV2 creates a new book
-func (a *App) CreateBookV2(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- return
- }
-
- var params createBookPayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handleError(w, "decoding payload", err, http.StatusInternalServerError)
- return
- }
-
- err = validateCreateBookPayload(params)
- if err != nil {
- handleError(w, "validating payload", err, http.StatusBadRequest)
- return
- }
-
- db := database.DBConn
-
- var bookCount int
- err = db.Model(database.Book{}).
- Where("user_id = ? AND label = ?", user.ID, params.Name).
- Count(&bookCount).Error
- if err != nil {
- handleError(w, "checking duplicate", err, http.StatusInternalServerError)
- return
- }
- if bookCount > 0 {
- http.Error(w, "duplicate book exists", http.StatusConflict)
- return
- }
-
- book, err := operations.CreateBook(user, a.Clock, params.Name)
- if err != nil {
- handleError(w, "inserting book", err, http.StatusInternalServerError)
- }
- resp := CreateBookResp{
- Book: presenters.PresentBook(book),
- }
- respondJSON(w, resp)
-}
-
-// BooksOptionsV2 is a handler for OPTIONS endpoint for notes
-func (a *App) BooksOptionsV2(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "POST")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
-}
diff --git a/pkg/server/api/handlers/v2_notes.go b/pkg/server/api/handlers/v2_notes.go
deleted file mode 100644
index eafb05bf..00000000
--- a/pkg/server/api/handlers/v2_notes.go
+++ /dev/null
@@ -1,100 +0,0 @@
-/* Copyright (C) 2019 Monomax Software Pty Ltd
- *
- * This file is part of Dnote.
- *
- * Dnote is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Dnote is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Dnote. If not, see .
- */
-
-package handlers
-
-import (
- "encoding/json"
- "net/http"
-
- "github.com/dnote/dnote/pkg/server/api/helpers"
- "github.com/dnote/dnote/pkg/server/api/operations"
- "github.com/dnote/dnote/pkg/server/api/presenters"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/pkg/errors"
-)
-
-type createNoteV2Payload struct {
- BookUUID string `json:"book_uuid"`
- Content string `json:"content"`
- AddedOn *int64 `json:"added_on"`
- EditedOn *int64 `json:"edited_on"`
-}
-
-func validateCreateNoteV2Payload(p createNoteV2Payload) error {
- if p.BookUUID == "" {
- return errors.New("bookUUID is required")
- }
-
- return nil
-}
-
-// CreateNoteV2Resp is a response for creating a note
-type CreateNoteV2Resp struct {
- Result presenters.Note `json:"result"`
-}
-
-// CreateNoteV2 creates a note
-func (a *App) CreateNoteV2(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var params createNoteV2Payload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handleError(w, "decoding payload", err, http.StatusInternalServerError)
- return
- }
-
- err = validateCreateNoteV2Payload(params)
- if err != nil {
- handleError(w, "validating payload", err, http.StatusBadRequest)
- return
- }
-
- var book database.Book
- db := database.DBConn
- if err := db.Where("uuid = ? AND user_id = ?", params.BookUUID, user.ID).First(&book).Error; err != nil {
- handleError(w, "finding book", err, http.StatusInternalServerError)
- return
- }
-
- note, err := operations.CreateNote(user, a.Clock, params.BookUUID, params.Content, params.AddedOn, params.EditedOn, false)
- if err != nil {
- handleError(w, "creating note", err, http.StatusInternalServerError)
- return
- }
-
- // preload associations
- note.User = user
- note.Book = book
-
- resp := CreateNoteV2Resp{
- Result: presenters.PresentNote(note),
- }
- respondJSON(w, resp)
-}
-
-// NotesOptionsV2 is a handler for OPTIONS endpoint for notes
-func (a *App) NotesOptionsV2(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "POST")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
-}
diff --git a/pkg/server/api/handlers/v1_auth.go b/pkg/server/api/handlers/v3_auth.go
similarity index 58%
rename from pkg/server/api/handlers/v1_auth.go
rename to pkg/server/api/handlers/v3_auth.go
index c7207d48..45af3fdd 100644
--- a/pkg/server/api/handlers/v1_auth.go
+++ b/pkg/server/api/handlers/v3_auth.go
@@ -23,11 +23,10 @@ import (
"net/http"
"time"
- "github.com/dnote/dnote/pkg/server/api/crypt"
"github.com/dnote/dnote/pkg/server/api/operations"
"github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/log"
"github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
)
// ErrLoginFailure is an error for failed login
@@ -35,14 +34,8 @@ var ErrLoginFailure = errors.New("Wrong email and password combination")
// SessionResponse is a response containing a session information
type SessionResponse struct {
- Key string `json:"key"`
- ExpiresAt int64 `json:"expires_at"`
- CipherKeyEnc string `json:"cipher_key_enc"`
-}
-
-type signinPayload struct {
- Email string `json:"email"`
- AuthKey string `json:"auth_key"`
+ Key string `json:"key"`
+ ExpiresAt int64 `json:"expires_at"`
}
func setSessionCookie(w http.ResponseWriter, key string, expires time.Time) {
@@ -70,16 +63,32 @@ func unsetSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &cookie)
}
+func touchLastLoginAt(user database.User) error {
+ db := database.DBConn
+
+ t := time.Now()
+ if err := db.Model(&user).Update(database.User{LastLoginAt: &t}).Error; err != nil {
+ return errors.Wrap(err, "updating last_login_at")
+ }
+
+ return nil
+}
+
+type signinPayload struct {
+ Email string `json:"email"`
+ Password string `json:"password"`
+}
+
func (a *App) signin(w http.ResponseWriter, r *http.Request) {
db := database.DBConn
var params signinPayload
- if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
+ err := json.NewDecoder(r.Body).Decode(¶ms)
+ if err != nil {
handleError(w, "decoding payload", err, http.StatusInternalServerError)
return
}
-
- if params.Email == "" || params.AuthKey == "" {
+ if params.Email == "" || params.Password == "" {
http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
return
}
@@ -89,21 +98,32 @@ func (a *App) signin(w http.ResponseWriter, r *http.Request) {
if conn.RecordNotFound() {
http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
return
- } else if err := conn.Error; err != nil {
+ } else if conn.Error != nil {
handleError(w, "getting user", err, http.StatusInternalServerError)
return
}
- authKeyHash := crypt.HashAuthKey(params.AuthKey, account.Salt, account.ServerKDFIteration)
- if account.AuthKeyHash != authKeyHash {
- log.WithFields(log.Fields{
- "account_id": account.ID,
- }).Error("Sign in password mismatch")
+ password := []byte(params.Password)
+ err = bcrypt.CompareHashAndPassword([]byte(account.Password.String), password)
+ if err != nil {
http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
return
}
- respondWithSession(w, account.UserID, account.CipherKeyEnc)
+ var user database.User
+ err = db.Where("id = ?", account.UserID).First(&user).Error
+ if err != nil {
+ handleError(w, "finding user", err, http.StatusInternalServerError)
+ return
+ }
+
+ err = operations.TouchLastLoginAt(user, db)
+ if err != nil {
+ http.Error(w, errors.Wrap(err, "touching login timestamp").Error(), http.StatusInternalServerError)
+ return
+ }
+
+ respondWithSession(w, account.UserID, http.StatusOK)
}
func (a *App) signoutOptions(w http.ResponseWriter, r *http.Request) {
@@ -134,45 +154,45 @@ func (a *App) signout(w http.ResponseWriter, r *http.Request) {
}
type registerPayload struct {
- Email string `json:"email"`
- AuthKey string `json:"auth_key"`
- Iteration int `json:"iteration"`
- CipherKeyEnc string `json:"cipher_key_enc"`
+ Email string `json:"email"`
+ Password string `json:"password"`
}
func validateRegisterPayload(p registerPayload) error {
if p.Email == "" {
return errors.New("email is required")
}
- if p.AuthKey == "" {
- return errors.New("auth_key is required")
- }
- if p.Iteration == 0 {
- return errors.New("iteration is required")
- }
- if p.CipherKeyEnc == "" {
- return errors.New("cipher_key_enc is required")
+ if len(p.Password) < 8 {
+ return errors.New("Password should be longer than 8 characters")
}
return nil
}
+func parseRegisterPaylaod(r *http.Request) (registerPayload, bool) {
+ var ret registerPayload
+ if err := json.NewDecoder(r.Body).Decode(&ret); err != nil {
+ return ret, false
+ }
+ if err := validateRegisterPayload(ret); err != nil {
+ return ret, false
+ }
+
+ return ret, true
+}
+
func (a *App) register(w http.ResponseWriter, r *http.Request) {
db := database.DBConn
- var params registerPayload
- if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
- handleError(w, "decoding payload", err, http.StatusInternalServerError)
- return
- }
- if err := validateRegisterPayload(params); err != nil {
- handleError(w, "validating payload", err, http.StatusBadRequest)
+ params, ok := parseRegisterPaylaod(r)
+ if !ok {
+ http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
var count int
if err := db.Model(database.Account{}).Where("email = ?", params.Email).Count(&count).Error; err != nil {
- handleError(w, "checking duplicate", err, http.StatusInternalServerError)
+ handleError(w, "checking duplicate user", err, http.StatusInternalServerError)
return
}
if count > 0 {
@@ -180,31 +200,18 @@ func (a *App) register(w http.ResponseWriter, r *http.Request) {
return
}
- tx := db.Begin()
-
- user, err := operations.CreateUser(tx, params.Email, params.AuthKey, params.CipherKeyEnc, params.Iteration)
+ user, err := operations.CreateUser(params.Email, params.Password)
if err != nil {
- tx.Rollback()
-
- handleError(w, "creating user", nil, http.StatusBadRequest)
+ handleError(w, "creating user", err, http.StatusInternalServerError)
return
}
- var account database.Account
- if err := tx.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
- tx.Rollback()
- handleError(w, "finding account", nil, http.StatusBadRequest)
- return
- }
-
- tx.Commit()
-
- respondWithSession(w, user.ID, account.CipherKeyEnc)
+ respondWithSession(w, user.ID, http.StatusCreated)
}
// respondWithSession makes a HTTP response with the session from the user with the given userID.
// It sets the HTTP-Only cookie for browser clients and also sends a JSON response for non-browser clients.
-func respondWithSession(w http.ResponseWriter, userID int, cipherKeyEnc string) {
+func respondWithSession(w http.ResponseWriter, userID int, statusCode int) {
db := database.DBConn
session, err := operations.CreateSession(db, userID)
@@ -216,53 +223,14 @@ func respondWithSession(w http.ResponseWriter, userID int, cipherKeyEnc string)
setSessionCookie(w, session.Key, session.ExpiresAt)
response := SessionResponse{
- Key: session.Key,
- ExpiresAt: session.ExpiresAt.Unix(),
- CipherKeyEnc: cipherKeyEnc,
+ Key: session.Key,
+ ExpiresAt: session.ExpiresAt.Unix(),
}
+
w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(statusCode)
if err := json.NewEncoder(w).Encode(response); err != nil {
handleError(w, "encoding response", err, http.StatusInternalServerError)
return
}
}
-
-// PresigninResponse is a response for presignin
-type PresigninResponse struct {
- Iteration int `json:"iteration"`
-}
-
-func (a *App) presignin(w http.ResponseWriter, r *http.Request) {
- db := database.DBConn
-
- q := r.URL.Query()
- email := q.Get("email")
- if email == "" {
- http.Error(w, "email is required", http.StatusBadRequest)
- return
- }
-
- var account database.Account
- conn := db.Where("email = ?", email).First(&account)
- if !conn.RecordNotFound() && conn.Error != nil {
- handleError(w, "getting user", conn.Error, http.StatusInternalServerError)
- return
- }
-
- var response PresigninResponse
- if conn.RecordNotFound() {
- response = PresigninResponse{
- Iteration: 100000,
- }
- } else {
- response = PresigninResponse{
- Iteration: account.ClientKDFIteration,
- }
- }
-
- w.Header().Set("Content-Type", "application/json")
- if err := json.NewEncoder(w).Encode(response); err != nil {
- handleError(w, "encoding response", nil, http.StatusInternalServerError)
- return
- }
-}
diff --git a/pkg/server/api/handlers/v3_auth_test.go b/pkg/server/api/handlers/v3_auth_test.go
new file mode 100644
index 00000000..f800089c
--- /dev/null
+++ b/pkg/server/api/handlers/v3_auth_test.go
@@ -0,0 +1,417 @@
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/mailer"
+ "github.com/dnote/dnote/pkg/server/testutils"
+ "github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
+)
+
+func init() {
+ testutils.InitTestDB()
+
+ templatePath := fmt.Sprintf("%s/mailer/templates/src", testutils.ServerPath)
+ mailer.InitTemplates(&templatePath)
+}
+
+func assertSessionResp(t *testing.T, res *http.Response) {
+ // after register, should sign in user
+ var got SessionResponse
+ if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var sessionCount int
+ var session database.Session
+ db := database.DBConn
+ testutils.MustExec(t, db.Model(&database.Session{}).Count(&sessionCount), "counting session")
+ testutils.MustExec(t, db.First(&session), "getting session")
+
+ assert.Equal(t, sessionCount, 1, "sessionCount mismatch")
+ assert.Equal(t, got.Key, session.Key, "session Key mismatch")
+ assert.Equal(t, got.ExpiresAt, session.ExpiresAt.Unix(), "session ExpiresAt mismatch")
+
+ c := testutils.GetCookieByName(res.Cookies(), "id")
+ assert.Equal(t, c.Value, session.Key, "session key mismatch")
+ assert.Equal(t, c.Path, "/", "session path mismatch")
+ assert.Equal(t, c.HttpOnly, true, "session HTTPOnly mismatch")
+ assert.Equal(t, c.Expires.Unix(), session.ExpiresAt.Unix(), "session Expires mismatch")
+}
+
+func TestRegister(t *testing.T) {
+ testCases := []struct {
+ email string
+ password string
+ }{
+ {
+ email: "alice@example.com",
+ password: "pass1234",
+ },
+ {
+ email: "bob@example.com",
+ password: "Y9EwmjH@Jq6y5a64MSACUoM4w7SAhzvY",
+ },
+ {
+ email: "chuck@example.com",
+ password: "e*H@kJi^vXbWEcD9T5^Am!Y@7#Po2@PC",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("register %s %s", tc.email, tc.password), func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ dat := fmt.Sprintf(`{"email": "%s", "password": "%s"}`, tc.email, tc.password)
+ req := testutils.MakeReq(server, "POST", "/v3/register", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusCreated, "")
+
+ var account database.Account
+ testutils.MustExec(t, db.Where("email = ?", tc.email).First(&account), "finding account")
+ assert.Equal(t, account.Email.String, tc.email, "Email mismatch")
+ assert.NotEqual(t, account.UserID, 0, "UserID mismatch")
+ passwordErr := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte(tc.password))
+ assert.Equal(t, passwordErr, nil, "Password mismatch")
+
+ var user database.User
+ testutils.MustExec(t, db.Where("id = ?", account.UserID).First(&user), "finding user")
+ assert.Equal(t, user.Cloud, false, "Cloud mismatch")
+ assert.Equal(t, user.StripeCustomerID, "", "StripeCustomerID mismatch")
+ assert.Equal(t, user.MaxUSN, 0, "MaxUSN mismatch")
+
+ // after register, should sign in user
+ assertSessionResp(t, res)
+ })
+ }
+}
+
+func TestRegisterMissingParams(t *testing.T) {
+ t.Run("missing email", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ dat := fmt.Sprintf(`{"password": %s}`, "SLMZFM5RmSjA5vfXnG5lPOnrpZSbtmV76cnAcrlr2yU")
+ req := testutils.MakeReq(server, "POST", "/v3/register", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status mismatch")
+
+ var accountCount, userCount int
+ testutils.MustExec(t, db.Model(&database.Account{}).Count(&accountCount), "counting account")
+ testutils.MustExec(t, db.Model(&database.User{}).Count(&userCount), "counting user")
+
+ assert.Equal(t, accountCount, 0, "accountCount mismatch")
+ assert.Equal(t, userCount, 0, "userCount mismatch")
+ })
+
+ t.Run("missing password", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ dat := fmt.Sprintf(`{"email": "%s"}`, "alice@example.com")
+ req := testutils.MakeReq(server, "POST", "/v3/register", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status mismatch")
+
+ var accountCount, userCount int
+ testutils.MustExec(t, db.Model(&database.Account{}).Count(&accountCount), "counting account")
+ testutils.MustExec(t, db.Model(&database.User{}).Count(&userCount), "counting user")
+
+ assert.Equal(t, accountCount, 0, "accountCount mismatch")
+ assert.Equal(t, userCount, 0, "userCount mismatch")
+ })
+}
+
+func TestRegisterDuplicateEmail(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupAccountData(u, "alice@example.com", "somepassword")
+
+ dat := `{"email": "alice@example.com", "password": "foobarbaz"}`
+ req := testutils.MakeReq(server, "POST", "/v3/register", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusBadRequest, "status code mismatch")
+
+ var accountCount, userCount, verificationTokenCount int
+ testutils.MustExec(t, db.Model(&database.Account{}).Count(&accountCount), "counting account")
+ testutils.MustExec(t, db.Model(&database.User{}).Count(&userCount), "counting user")
+ testutils.MustExec(t, db.Model(&database.Token{}).Count(&verificationTokenCount), "counting verification token")
+
+ var user database.User
+ testutils.MustExec(t, db.Where("id = ?", u.ID).First(&user), "finding user")
+
+ assert.Equal(t, accountCount, 1, "account count mismatch")
+ assert.Equal(t, userCount, 1, "user count mismatch")
+ assert.Equal(t, verificationTokenCount, 0, "verification_token should not have been created")
+ assert.Equal(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch")
+}
+
+func TestSignIn(t *testing.T) {
+ t.Run("success", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupAccountData(u, "alice@example.com", "pass1234")
+
+ dat := `{"email": "alice@example.com", "password": "pass1234"}`
+ req := testutils.MakeReq(server, "POST", "/v3/signin", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var user database.User
+ testutils.MustExec(t, db.Model(&database.User{}).First(&user), "finding user")
+ assert.NotEqual(t, user.LastLoginAt, nil, "LastLoginAt mismatch")
+
+ // after register, should sign in user
+ assertSessionResp(t, res)
+ })
+
+ t.Run("wrong password", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupAccountData(u, "alice@example.com", "pass1234")
+
+ dat := `{"email": "alice@example.com", "password": "wrongpassword1234"}`
+ req := testutils.MakeReq(server, "POST", "/v3/signin", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
+
+ var user database.User
+ testutils.MustExec(t, db.Model(&database.User{}).First(&user), "finding user")
+ assert.Equal(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch")
+
+ var sessionCount int
+ testutils.MustExec(t, db.Model(&database.Session{}).Count(&sessionCount), "counting session")
+ assert.Equal(t, sessionCount, 0, "sessionCount mismatch")
+ })
+
+ t.Run("wrong email", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ u := testutils.SetupUserData()
+ testutils.SetupAccountData(u, "alice@example.com", "pass1234")
+
+ dat := `{"email": "bob@example.com", "password": "pass1234"}`
+ req := testutils.MakeReq(server, "POST", "/v3/signin", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
+
+ var user database.User
+ testutils.MustExec(t, db.Model(&database.User{}).First(&user), "finding user")
+ assert.DeepEqual(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch")
+
+ var sessionCount int
+ testutils.MustExec(t, db.Model(&database.Session{}).Count(&sessionCount), "counting session")
+ assert.Equal(t, sessionCount, 0, "sessionCount mismatch")
+ })
+
+ t.Run("nonexistent email", func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ dat := `{"email": "nonexistent@example.com", "password": "pass1234"}`
+ req := testutils.MakeReq(server, "POST", "/v3/signin", dat)
+
+ // Execute
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
+
+ var sessionCount int
+ testutils.MustExec(t, db.Model(&database.Session{}).Count(&sessionCount), "counting session")
+ assert.Equal(t, sessionCount, 0, "sessionCount mismatch")
+ })
+}
+
+func TestSignout(t *testing.T) {
+ t.Run("authenticated", func(t *testing.T) {
+ db := database.DBConn
+ defer testutils.ClearData()
+
+ aliceUser := testutils.SetupUserData()
+ testutils.SetupAccountData(aliceUser, "alice@example.com", "pass1234")
+ anotherUser := testutils.SetupUserData()
+
+ session1 := database.Session{
+ Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=",
+ UserID: aliceUser.ID,
+ ExpiresAt: time.Now().Add(time.Hour * 24),
+ }
+ testutils.MustExec(t, db.Save(&session1), "preparing session1")
+ session2 := database.Session{
+ Key: "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=",
+ UserID: anotherUser.ID,
+ ExpiresAt: time.Now().Add(time.Hour * 24),
+ }
+ testutils.MustExec(t, db.Save(&session2), "preparing session2")
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ // Execute
+ req := testutils.MakeReq(server, "POST", "/v3/signout", "")
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU="))
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusNoContent, "Status mismatch")
+
+ var sessionCount int
+ var s2 database.Session
+ testutils.MustExec(t, db.Model(&database.Session{}).Count(&sessionCount), "counting session")
+ testutils.MustExec(t, db.Where("key = ?", "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=").First(&s2), "getting s2")
+
+ assert.Equal(t, sessionCount, 1, "sessionCount mismatch")
+
+ c := testutils.GetCookieByName(res.Cookies(), "id")
+ assert.Equal(t, c.Value, "", "session key mismatch")
+ assert.Equal(t, c.Path, "/", "session path mismatch")
+ assert.Equal(t, c.HttpOnly, true, "session HTTPOnly mismatch")
+ if c.Expires.After(time.Now()) {
+ t.Error("session cookie is not expired")
+ }
+ })
+
+ t.Run("unauthenticated", func(t *testing.T) {
+ db := database.DBConn
+ defer testutils.ClearData()
+
+ aliceUser := testutils.SetupUserData()
+ testutils.SetupAccountData(aliceUser, "alice@example.com", "pass1234")
+ anotherUser := testutils.SetupUserData()
+
+ session1 := database.Session{
+ Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=",
+ UserID: aliceUser.ID,
+ ExpiresAt: time.Now().Add(time.Hour * 24),
+ }
+ testutils.MustExec(t, db.Save(&session1), "preparing session1")
+ session2 := database.Session{
+ Key: "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=",
+ UserID: anotherUser.ID,
+ ExpiresAt: time.Now().Add(time.Hour * 24),
+ }
+ testutils.MustExec(t, db.Save(&session2), "preparing session2")
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ // Execute
+ req := testutils.MakeReq(server, "POST", "/v3/signout", "")
+ res := testutils.HTTPDo(t, req)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusNoContent, "Status mismatch")
+
+ var sessionCount int
+ var postSession1, postSession2 database.Session
+ testutils.MustExec(t, db.Model(&database.Session{}).Count(&sessionCount), "counting session")
+ testutils.MustExec(t, db.Where("key = ?", "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=").First(&postSession1), "getting postSession1")
+ testutils.MustExec(t, db.Where("key = ?", "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=").First(&postSession2), "getting postSession2")
+
+ // two existing sessions should remain
+ assert.Equal(t, sessionCount, 2, "sessionCount mismatch")
+
+ c := testutils.GetCookieByName(res.Cookies(), "id")
+ assert.Equal(t, c, (*http.Cookie)(nil), "id cookie should have not been set")
+ })
+}
diff --git a/pkg/server/api/handlers/v1_books.go b/pkg/server/api/handlers/v3_books.go
similarity index 84%
rename from pkg/server/api/handlers/v1_books.go
rename to pkg/server/api/handlers/v3_books.go
index 9d8d6cb3..df92c33e 100644
--- a/pkg/server/api/handlers/v1_books.go
+++ b/pkg/server/api/handlers/v3_books.go
@@ -32,6 +32,74 @@ import (
"github.com/pkg/errors"
)
+type createBookPayload struct {
+ Name string `json:"name"`
+}
+
+// CreateBookResp is the response from create book api
+type CreateBookResp struct {
+ Book presenters.Book `json:"book"`
+}
+
+func validateCreateBookPayload(p createBookPayload) error {
+ if p.Name == "" {
+ return errors.New("name is required")
+ }
+
+ return nil
+}
+
+// CreateBook creates a new book
+func (a *App) CreateBook(w http.ResponseWriter, r *http.Request) {
+ user, ok := r.Context().Value(helpers.KeyUser).(database.User)
+ if !ok {
+ return
+ }
+
+ var params createBookPayload
+ err := json.NewDecoder(r.Body).Decode(¶ms)
+ if err != nil {
+ handleError(w, "decoding payload", err, http.StatusInternalServerError)
+ return
+ }
+
+ err = validateCreateBookPayload(params)
+ if err != nil {
+ handleError(w, "validating payload", err, http.StatusBadRequest)
+ return
+ }
+
+ db := database.DBConn
+
+ var bookCount int
+ err = db.Model(database.Book{}).
+ Where("user_id = ? AND label = ?", user.ID, params.Name).
+ Count(&bookCount).Error
+ if err != nil {
+ handleError(w, "checking duplicate", err, http.StatusInternalServerError)
+ return
+ }
+ if bookCount > 0 {
+ http.Error(w, "duplicate book exists", http.StatusConflict)
+ return
+ }
+
+ book, err := operations.CreateBook(user, a.Clock, params.Name)
+ if err != nil {
+ handleError(w, "inserting book", err, http.StatusInternalServerError)
+ }
+ resp := CreateBookResp{
+ Book: presenters.PresentBook(book),
+ }
+ respondJSON(w, resp)
+}
+
+// BooksOptions is a handler for OPTIONS endpoint for notes
+func (a *App) BooksOptions(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
+ w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
+}
+
func respondWithBooks(userID int, query url.Values, w http.ResponseWriter) {
db := database.DBConn
@@ -117,29 +185,6 @@ func (a *App) GetBook(w http.ResponseWriter, r *http.Request) {
respondJSON(w, p)
}
-type createBookPayload struct {
- Name string `json:"name"`
-}
-
-// CreateBookResp is the response from create book api
-type CreateBookResp struct {
- Book presenters.Book `json:"book"`
-}
-
-func validateCreateBookPayload(p createBookPayload) error {
- if p.Name == "" {
- return errors.New("name is required")
- }
-
- return nil
-}
-
-// CreateBook creates a new book
-func (a *App) CreateBook(w http.ResponseWriter, r *http.Request) {
- http.Error(w, "Not supported. Please upgrade your client.", http.StatusGone)
- return
-}
-
type updateBookPayload struct {
Name *string `json:"name"`
}
@@ -240,9 +285,3 @@ func (a *App) DeleteBook(w http.ResponseWriter, r *http.Request) {
}
respondJSON(w, resp)
}
-
-// BooksOptions handles OPTIONS endpoint
-func (a *App) BooksOptions(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
-}
diff --git a/pkg/server/api/handlers/v3_books_test.go b/pkg/server/api/handlers/v3_books_test.go
new file mode 100644
index 00000000..fd71492a
--- /dev/null
+++ b/pkg/server/api/handlers/v3_books_test.go
@@ -0,0 +1,531 @@
+package handlers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/api/presenters"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/testutils"
+ "github.com/pkg/errors"
+)
+
+func init() {
+ testutils.InitTestDB()
+}
+
+func TestGetBooks(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ anotherUser := testutils.SetupUserData()
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ USN: 1123,
+ Deleted: false,
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: "css",
+ USN: 1125,
+ Deleted: false,
+ }
+ testutils.MustExec(t, db.Save(&b2), "preparing b2")
+ b3 := database.Book{
+ UserID: anotherUser.ID,
+ Label: "css",
+ USN: 1128,
+ Deleted: false,
+ }
+ testutils.MustExec(t, db.Save(&b3), "preparing b3")
+ b4 := database.Book{
+ UserID: user.ID,
+ Label: "",
+ USN: 1129,
+ Deleted: true,
+ }
+ testutils.MustExec(t, db.Save(&b4), "preparing b4")
+
+ // Execute
+ req := testutils.MakeReq(server, "GET", "/v3/books", "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var payload []presenters.Book
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var b1Record, b2Record database.Book
+ testutils.MustExec(t, db.Where("id = ?", b1.ID).First(&b1Record), "finding b1")
+ testutils.MustExec(t, db.Where("id = ?", b2.ID).First(&b2Record), "finding b2")
+ testutils.MustExec(t, db.Where("id = ?", b2.ID).First(&b2Record), "finding b2")
+
+ expected := []presenters.Book{
+ {
+ UUID: b2Record.UUID,
+ CreatedAt: b2Record.CreatedAt,
+ UpdatedAt: b2Record.UpdatedAt,
+ Label: b2Record.Label,
+ USN: b2Record.USN,
+ },
+ {
+ UUID: b1Record.UUID,
+ CreatedAt: b1Record.CreatedAt,
+ UpdatedAt: b1Record.UpdatedAt,
+ Label: b1Record.Label,
+ USN: b1Record.USN,
+ },
+ }
+
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
+}
+
+func TestGetBooksByName(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ anotherUser := testutils.SetupUserData()
+ req := testutils.MakeReq(server, "GET", "/v3/books?name=js", "")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, db.Save(&b2), "preparing b2")
+ b3 := database.Book{
+ UserID: anotherUser.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, db.Save(&b3), "preparing b3")
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var payload []presenters.Book
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var b1Record database.Book
+ testutils.MustExec(t, db.Where("id = ?", b1.ID).First(&b1Record), "finding b1")
+
+ expected := []presenters.Book{
+ {
+ UUID: b1Record.UUID,
+ CreatedAt: b1Record.CreatedAt,
+ UpdatedAt: b1Record.UpdatedAt,
+ Label: b1Record.Label,
+ USN: b1Record.USN,
+ },
+ }
+
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
+}
+
+func TestDeleteBook(t *testing.T) {
+ testCases := []struct {
+ label string
+ deleted bool
+ expectedB2USN int
+ expectedMaxUSN int
+ expectedN2USN int
+ expectedN3USN int
+ }{
+ {
+ label: "n1 content",
+ deleted: false,
+ expectedMaxUSN: 61,
+ expectedB2USN: 61,
+ expectedN2USN: 59,
+ expectedN3USN: 60,
+ },
+ {
+ label: "",
+ deleted: true,
+ expectedMaxUSN: 59,
+ expectedB2USN: 59,
+ expectedN2USN: 5,
+ expectedN3USN: 6,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&user).Update("max_usn", 58), "preparing user max_usn")
+ anotherUser := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&anotherUser).Update("max_usn", 109), "preparing another user max_usn")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ USN: 1,
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing a book data")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: tc.label,
+ USN: 2,
+ Deleted: tc.deleted,
+ }
+ testutils.MustExec(t, db.Save(&b2), "preparing a book data")
+ b3 := database.Book{
+ UserID: anotherUser.ID,
+ Label: "linux",
+ USN: 3,
+ }
+ testutils.MustExec(t, db.Save(&b3), "preparing a book data")
+
+ var n2Body string
+ if !tc.deleted {
+ n2Body = "n2 content"
+ }
+ var n3Body string
+ if !tc.deleted {
+ n3Body = "n3 content"
+ }
+
+ n1 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n1 content",
+ USN: 4,
+ }
+ testutils.MustExec(t, db.Save(&n1), "preparing a note data")
+ n2 := database.Note{
+ UserID: user.ID,
+ BookUUID: b2.UUID,
+ Body: n2Body,
+ USN: 5,
+ Deleted: tc.deleted,
+ }
+ testutils.MustExec(t, db.Save(&n2), "preparing a note data")
+ n3 := database.Note{
+ UserID: user.ID,
+ BookUUID: b2.UUID,
+ Body: n3Body,
+ USN: 6,
+ Deleted: tc.deleted,
+ }
+ testutils.MustExec(t, db.Save(&n3), "preparing a note data")
+ n4 := database.Note{
+ UserID: user.ID,
+ BookUUID: b2.UUID,
+ Body: "",
+ USN: 7,
+ Deleted: true,
+ }
+ testutils.MustExec(t, db.Save(&n4), "preparing a note data")
+ n5 := database.Note{
+ UserID: anotherUser.ID,
+ BookUUID: b3.UUID,
+ Body: "n5 content",
+ USN: 8,
+ }
+ testutils.MustExec(t, db.Save(&n5), "preparing a note data")
+
+ endpoint := fmt.Sprintf("/v3/books/%s", b2.UUID)
+ req := testutils.MakeReq(server, "DELETE", endpoint, "")
+ req.Header.Set("Version", "0.1.1")
+ req.Header.Set("Origin", "chrome-extension://iaolnfnipkoinabdbbakcmkkdignedce")
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var b1Record, b2Record, b3Record database.Book
+ var n1Record, n2Record, n3Record, n4Record, n5Record database.Note
+ var userRecord database.User
+ var bookCount, noteCount int
+
+ testutils.MustExec(t, db.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, db.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, db.Where("id = ?", b1.ID).First(&b1Record), "finding b1")
+ testutils.MustExec(t, db.Where("id = ?", b2.ID).First(&b2Record), "finding b2")
+ testutils.MustExec(t, db.Where("id = ?", b3.ID).First(&b3Record), "finding b3")
+ testutils.MustExec(t, db.Where("id = ?", n1.ID).First(&n1Record), "finding n1")
+ testutils.MustExec(t, db.Where("id = ?", n2.ID).First(&n2Record), "finding n2")
+ testutils.MustExec(t, db.Where("id = ?", n3.ID).First(&n3Record), "finding n3")
+ testutils.MustExec(t, db.Where("id = ?", n4.ID).First(&n4Record), "finding n4")
+ testutils.MustExec(t, db.Where("id = ?", n5.ID).First(&n5Record), "finding n5")
+ testutils.MustExec(t, db.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ assert.Equal(t, bookCount, 3, "book count mismatch")
+ assert.Equal(t, noteCount, 5, "note count mismatch")
+
+ assert.Equal(t, userRecord.MaxUSN, tc.expectedMaxUSN, "user max_usn mismatch")
+
+ assert.Equal(t, b1Record.Deleted, false, "b1 deleted mismatch")
+ assert.Equal(t, b1Record.Label, b1.Label, "b1 content mismatch")
+ assert.Equal(t, b1Record.USN, b1.USN, "b1 usn mismatch")
+ assert.Equal(t, b2Record.Deleted, true, "b2 deleted mismatch")
+ assert.Equal(t, b2Record.Label, "", "b2 content mismatch")
+ assert.Equal(t, b2Record.USN, tc.expectedB2USN, "b2 usn mismatch")
+ assert.Equal(t, b3Record.Deleted, false, "b3 deleted mismatch")
+ assert.Equal(t, b3Record.Label, b3.Label, "b3 content mismatch")
+ assert.Equal(t, b3Record.USN, b3.USN, "b3 usn mismatch")
+
+ assert.Equal(t, n1Record.USN, n1.USN, "n1 usn mismatch")
+ assert.Equal(t, n1Record.Deleted, false, "n1 deleted mismatch")
+ assert.Equal(t, n1Record.Body, n1.Body, "n1 content mismatch")
+
+ assert.Equal(t, n2Record.USN, tc.expectedN2USN, "n2 usn mismatch")
+ assert.Equal(t, n2Record.Deleted, true, "n2 deleted mismatch")
+ assert.Equal(t, n2Record.Body, "", "n2 content mismatch")
+
+ assert.Equal(t, n3Record.USN, tc.expectedN3USN, "n3 usn mismatch")
+ assert.Equal(t, n3Record.Deleted, true, "n3 deleted mismatch")
+ assert.Equal(t, n3Record.Body, "", "n3 content mismatch")
+
+ // if already deleted, usn should remain the same and hence should not contribute to bumping the max_usn
+ assert.Equal(t, n4Record.USN, n4.USN, "n4 usn mismatch")
+ assert.Equal(t, n4Record.Deleted, true, "n4 deleted mismatch")
+ assert.Equal(t, n4Record.Body, "", "n4 content mismatch")
+
+ assert.Equal(t, n5Record.USN, n5.USN, "n5 usn mismatch")
+ assert.Equal(t, n5Record.Deleted, false, "n5 deleted mismatch")
+ assert.Equal(t, n5Record.Body, n5.Body, "n5 content mismatch")
+ })
+ }
+}
+
+func TestCreateBook(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&user).Update("max_usn", 101), "preparing user max_usn")
+
+ req := testutils.MakeReq(server, "POST", "/v3/books", `{"name": "js"}`)
+ req.Header.Set("Version", "0.1.1")
+ req.Header.Set("Origin", "chrome-extension://iaolnfnipkoinabdbbakcmkkdignedce")
+
+ // Execute
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var bookRecord database.Book
+ var userRecord database.User
+ var bookCount, noteCount int
+ testutils.MustExec(t, db.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, db.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, db.First(&bookRecord), "finding book")
+ testutils.MustExec(t, db.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ maxUSN := 102
+
+ assert.Equalf(t, bookCount, 1, "book count mismatch")
+ assert.Equalf(t, noteCount, 0, "note count mismatch")
+
+ assert.NotEqual(t, bookRecord.UUID, "", "book uuid should have been generated")
+ assert.Equal(t, bookRecord.Label, "js", "book name mismatch")
+ assert.Equal(t, bookRecord.UserID, user.ID, "book user_id mismatch")
+ assert.Equal(t, bookRecord.USN, maxUSN, "book user_id mismatch")
+ assert.Equal(t, userRecord.MaxUSN, maxUSN, "user max_usn mismatch")
+
+ var got CreateBookResp
+ if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding got"))
+ }
+ expected := CreateBookResp{
+ Book: presenters.Book{
+ UUID: bookRecord.UUID,
+ USN: bookRecord.USN,
+ CreatedAt: bookRecord.CreatedAt,
+ UpdatedAt: bookRecord.UpdatedAt,
+ Label: "js",
+ },
+ }
+
+ if ok := reflect.DeepEqual(got, expected); !ok {
+ t.Errorf("Payload does not match.\nActual: %+v\nExpected: %+v", got, expected)
+ }
+}
+
+func TestCreateBookDuplicate(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&user).Update("max_usn", 101), "preparing user max_usn")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ USN: 58,
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing book data")
+
+ // Execute
+ req := testutils.MakeReq(server, "POST", "/v3/books", `{"name": "js"}`)
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusConflict, "")
+
+ var bookRecord database.Book
+ var bookCount, noteCount int
+ var userRecord database.User
+ testutils.MustExec(t, db.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, db.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, db.First(&bookRecord), "finding book")
+ testutils.MustExec(t, db.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ assert.Equalf(t, bookCount, 1, "book count mismatch")
+ assert.Equalf(t, noteCount, 0, "note count mismatch")
+
+ assert.Equal(t, bookRecord.Label, "js", "book name mismatch")
+ assert.Equal(t, bookRecord.UserID, user.ID, "book user_id mismatch")
+ assert.Equal(t, bookRecord.USN, b1.USN, "book usn mismatch")
+ assert.Equal(t, userRecord.MaxUSN, 101, "user max_usn mismatch")
+}
+
+func TestUpdateBook(t *testing.T) {
+ updatedLabel := "updated-label"
+
+ b1UUID := "ead8790f-aff9-4bdf-8eec-f734ccd29202"
+ b2UUID := "0ecaac96-8d72-4e04-8925-5a21b79a16da"
+
+ testCases := []struct {
+ payload string
+ bookUUID string
+ bookDeleted bool
+ bookLabel string
+ expectedBookLabel string
+ }{
+ {
+ payload: fmt.Sprintf(`{
+ "name": "%s"
+ }`, updatedLabel),
+ bookUUID: b1UUID,
+ bookDeleted: false,
+ bookLabel: "original-label",
+ expectedBookLabel: updatedLabel,
+ },
+ // if a deleted book is updated, it should be un-deleted
+ {
+ payload: fmt.Sprintf(`{
+ "name": "%s"
+ }`, updatedLabel),
+ bookUUID: b1UUID,
+ bookDeleted: true,
+ bookLabel: "",
+ expectedBookLabel: updatedLabel,
+ },
+ }
+
+ for idx, tc := range testCases {
+ func() {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&user).Update("max_usn", 101), "preparing user max_usn")
+
+ b1 := database.Book{
+ UUID: tc.bookUUID,
+ UserID: user.ID,
+ Label: tc.bookLabel,
+ Deleted: tc.bookDeleted,
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UUID: b2UUID,
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, db.Save(&b2), "preparing b2")
+
+ // Execute
+ endpoint := fmt.Sprintf("/v3/books/%s", tc.bookUUID)
+ req := testutils.MakeReq(server, "PATCH", endpoint, tc.payload)
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, fmt.Sprintf("status code mismatch for test case %d", idx))
+
+ var bookRecord database.Book
+ var userRecord database.User
+ var noteCount, bookCount int
+ testutils.MustExec(t, db.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, db.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, db.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
+ testutils.MustExec(t, db.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ assert.Equalf(t, bookCount, 2, "book count mismatch")
+ assert.Equalf(t, noteCount, 0, "note count mismatch")
+
+ assert.Equalf(t, bookRecord.UUID, tc.bookUUID, "book uuid mismatch")
+ assert.Equalf(t, bookRecord.Label, tc.expectedBookLabel, "book label mismatch")
+ assert.Equalf(t, bookRecord.USN, 102, "book usn mismatch")
+ assert.Equalf(t, bookRecord.Deleted, false, "book Deleted mismatch")
+
+ assert.Equal(t, userRecord.MaxUSN, 102, fmt.Sprintf("user max_usn mismatch for test case %d", idx))
+ }()
+ }
+}
diff --git a/pkg/server/api/handlers/v1_notes.go b/pkg/server/api/handlers/v3_notes.go
similarity index 72%
rename from pkg/server/api/handlers/v1_notes.go
rename to pkg/server/api/handlers/v3_notes.go
index af5646e3..85dba132 100644
--- a/pkg/server/api/handlers/v1_notes.go
+++ b/pkg/server/api/handlers/v3_notes.go
@@ -28,20 +28,9 @@ import (
"github.com/dnote/dnote/pkg/server/api/presenters"
"github.com/dnote/dnote/pkg/server/database"
"github.com/gorilla/mux"
+ "github.com/pkg/errors"
)
-// CreateNote creates a note by generating an action and feeding it to the reducer
-func (a *App) CreateNote(w http.ResponseWriter, r *http.Request) {
- http.Error(w, "Not supported. Please upgrade your client.", http.StatusGone)
- return
-}
-
-// NotesOptions is a handler for OPTIONS endpoint for notes
-func (a *App) NotesOptions(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "POST")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
-}
-
type updateNotePayload struct {
BookUUID *string `json:"book_uuid"`
Content *string `json:"content"`
@@ -156,3 +145,73 @@ func (a *App) DeleteNote(w http.ResponseWriter, r *http.Request) {
}
respondJSON(w, resp)
}
+
+type createNotePayload struct {
+ BookUUID string `json:"book_uuid"`
+ Content string `json:"content"`
+ AddedOn *int64 `json:"added_on"`
+ EditedOn *int64 `json:"edited_on"`
+}
+
+func validateCreateNotePayload(p createNotePayload) error {
+ if p.BookUUID == "" {
+ return errors.New("bookUUID is required")
+ }
+
+ return nil
+}
+
+// CreateNoteResp is a response for creating a note
+type CreateNoteResp struct {
+ Result presenters.Note `json:"result"`
+}
+
+// CreateNote creates a note
+func (a *App) CreateNote(w http.ResponseWriter, r *http.Request) {
+ user, ok := r.Context().Value(helpers.KeyUser).(database.User)
+ if !ok {
+ handleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
+ return
+ }
+
+ var params createNotePayload
+ err := json.NewDecoder(r.Body).Decode(¶ms)
+ if err != nil {
+ handleError(w, "decoding payload", err, http.StatusInternalServerError)
+ return
+ }
+
+ err = validateCreateNotePayload(params)
+ if err != nil {
+ handleError(w, "validating payload", err, http.StatusBadRequest)
+ return
+ }
+
+ var book database.Book
+ db := database.DBConn
+ if err := db.Where("uuid = ? AND user_id = ?", params.BookUUID, user.ID).First(&book).Error; err != nil {
+ handleError(w, "finding book", err, http.StatusInternalServerError)
+ return
+ }
+
+ note, err := operations.CreateNote(user, a.Clock, params.BookUUID, params.Content, params.AddedOn, params.EditedOn, false)
+ if err != nil {
+ handleError(w, "creating note", err, http.StatusInternalServerError)
+ return
+ }
+
+ // preload associations
+ note.User = user
+ note.Book = book
+
+ resp := CreateNoteResp{
+ Result: presenters.PresentNote(note),
+ }
+ respondJSON(w, resp)
+}
+
+// NotesOptions is a handler for OPTIONS endpoint for notes
+func (a *App) NotesOptions(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Methods", "POST")
+ w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
+}
diff --git a/pkg/server/api/handlers/v3_notes_test.go b/pkg/server/api/handlers/v3_notes_test.go
new file mode 100644
index 00000000..323a2ed9
--- /dev/null
+++ b/pkg/server/api/handlers/v3_notes_test.go
@@ -0,0 +1,304 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/dnote/dnote/pkg/assert"
+ "github.com/dnote/dnote/pkg/clock"
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/dnote/dnote/pkg/server/testutils"
+)
+
+func init() {
+ testutils.InitTestDB()
+}
+
+func TestCreateNote(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&user).Update("max_usn", 101), "preparing user max_usn")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ USN: 58,
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+
+ // Execute
+ dat := fmt.Sprintf(`{"book_uuid": "%s", "content": "note content"}`, b1.UUID)
+ req := testutils.MakeReq(server, "POST", "/v3/notes", dat)
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var noteRecord database.Note
+ var bookRecord database.Book
+ var userRecord database.User
+ var bookCount, noteCount int
+ testutils.MustExec(t, db.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, db.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, db.First(¬eRecord), "finding note")
+ testutils.MustExec(t, db.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
+ testutils.MustExec(t, db.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ assert.Equalf(t, bookCount, 1, "book count mismatch")
+ assert.Equalf(t, noteCount, 1, "note count mismatch")
+
+ assert.Equal(t, bookRecord.Label, b1.Label, "book name mismatch")
+ assert.Equal(t, bookRecord.UUID, b1.UUID, "book uuid mismatch")
+ assert.Equal(t, bookRecord.UserID, b1.UserID, "book user_id mismatch")
+ assert.Equal(t, bookRecord.USN, 58, "book usn mismatch")
+
+ assert.NotEqual(t, noteRecord.UUID, "", "note uuid should have been generated")
+ assert.Equal(t, noteRecord.BookUUID, b1.UUID, "note book_uuid mismatch")
+ assert.Equal(t, noteRecord.Body, "note content", "note content mismatch")
+ assert.Equal(t, noteRecord.USN, 102, "note usn mismatch")
+}
+
+func TestUpdateNote(t *testing.T) {
+ updatedBody := "some updated content"
+
+ b1UUID := "37868a8e-a844-4265-9a4f-0be598084733"
+ b2UUID := "8f3bd424-6aa5-4ed5-910d-e5b38ab09f8c"
+
+ testCases := []struct {
+ payload string
+ noteUUID string
+ noteBookUUID string
+ noteBody string
+ noteDeleted bool
+ expectedNoteBody string
+ expectedNoteBookName string
+ expectedNoteBookUUID string
+ }{
+ {
+ payload: fmt.Sprintf(`{
+ "content": "%s"
+ }`, updatedBody),
+ noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
+ noteBookUUID: b1UUID,
+ noteBody: "original content",
+ noteDeleted: false,
+ expectedNoteBookUUID: b1UUID,
+ expectedNoteBody: "some updated content",
+ expectedNoteBookName: "css",
+ },
+ {
+ payload: fmt.Sprintf(`{
+ "book_uuid": "%s"
+ }`, b1UUID),
+ noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
+ noteBookUUID: b1UUID,
+ noteBody: "original content",
+ noteDeleted: false,
+ expectedNoteBookUUID: b1UUID,
+ expectedNoteBody: "original content",
+ expectedNoteBookName: "css",
+ },
+ {
+ payload: fmt.Sprintf(`{
+ "book_uuid": "%s"
+ }`, b2UUID),
+ noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
+ noteBookUUID: b1UUID,
+ noteBody: "original content",
+ noteDeleted: false,
+ expectedNoteBookUUID: b2UUID,
+ expectedNoteBody: "original content",
+ expectedNoteBookName: "js",
+ },
+ {
+ payload: fmt.Sprintf(`{
+ "book_uuid": "%s",
+ "content": "%s"
+ }`, b2UUID, updatedBody),
+ noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
+ noteBookUUID: b1UUID,
+ noteBody: "original content",
+ noteDeleted: false,
+ expectedNoteBookUUID: b2UUID,
+ expectedNoteBody: "some updated content",
+ expectedNoteBookName: "js",
+ },
+ {
+ payload: fmt.Sprintf(`{
+ "book_uuid": "%s",
+ "content": "%s"
+ }`, b1UUID, updatedBody),
+ noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
+ noteBookUUID: b1UUID,
+ noteBody: "",
+ noteDeleted: true,
+ expectedNoteBookUUID: b1UUID,
+ expectedNoteBody: updatedBody,
+ expectedNoteBookName: "js",
+ },
+ }
+
+ for idx, tc := range testCases {
+ func() {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&user).Update("max_usn", 101), "preparing user max_usn")
+
+ b1 := database.Book{
+ UUID: b1UUID,
+ UserID: user.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UUID: b2UUID,
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, db.Save(&b2), "preparing b2")
+
+ note := database.Note{
+ UserID: user.ID,
+ UUID: tc.noteUUID,
+ BookUUID: tc.noteBookUUID,
+ Body: tc.noteBody,
+ Deleted: tc.noteDeleted,
+ }
+ testutils.MustExec(t, db.Save(¬e), "preparing note")
+
+ // Execute
+ endpoint := fmt.Sprintf("/v3/notes/%s", note.UUID)
+ req := testutils.MakeReq(server, "PATCH", endpoint, tc.payload)
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, fmt.Sprintf("status code mismatch for test case %d", idx))
+
+ var bookRecord database.Book
+ var noteRecord database.Note
+ var userRecord database.User
+ var noteCount, bookCount int
+ testutils.MustExec(t, db.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, db.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, db.Where("uuid = ?", note.UUID).First(¬eRecord), "finding note")
+ testutils.MustExec(t, db.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
+ testutils.MustExec(t, db.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ assert.Equalf(t, bookCount, 2, "book count mismatch")
+ assert.Equalf(t, noteCount, 1, "note count mismatch")
+
+ assert.Equal(t, noteRecord.UUID, tc.noteUUID, fmt.Sprintf("note uuid mismatch for test case %d", idx))
+ assert.Equal(t, noteRecord.Body, tc.expectedNoteBody, fmt.Sprintf("note content mismatch for test case %d", idx))
+ assert.Equal(t, noteRecord.BookUUID, tc.expectedNoteBookUUID, fmt.Sprintf("note book_uuid mismatch for test case %d", idx))
+ assert.Equal(t, noteRecord.USN, 102, fmt.Sprintf("note usn mismatch for test case %d", idx))
+
+ assert.Equal(t, userRecord.MaxUSN, 102, fmt.Sprintf("user max_usn mismatch for test case %d", idx))
+ }()
+ }
+}
+
+func TestDeleteNote(t *testing.T) {
+ b1UUID := "37868a8e-a844-4265-9a4f-0be598084733"
+
+ testCases := []struct {
+ content string
+ deleted bool
+ originalUSN int
+ expectedUSN int
+ expectedMaxUSN int
+ }{
+ {
+ content: "n1 content",
+ deleted: false,
+ originalUSN: 12,
+ expectedUSN: 982,
+ expectedMaxUSN: 982,
+ },
+ {
+ content: "",
+ deleted: true,
+ originalUSN: 12,
+ expectedUSN: 982,
+ expectedMaxUSN: 982,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T) {
+ defer testutils.ClearData()
+ db := database.DBConn
+
+ // Setup
+ server := httptest.NewServer(NewRouter(&App{
+ Clock: clock.NewMock(),
+ }))
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.MustExec(t, db.Model(&user).Update("max_usn", 981), "preparing user max_usn")
+
+ b1 := database.Book{
+ UUID: b1UUID,
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, db.Save(&b1), "preparing b1")
+ note := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: tc.content,
+ Deleted: tc.deleted,
+ USN: tc.originalUSN,
+ }
+ testutils.MustExec(t, db.Save(¬e), "preparing note")
+
+ // Execute
+ endpoint := fmt.Sprintf("/v3/notes/%s", note.UUID)
+ req := testutils.MakeReq(server, "DELETE", endpoint, "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var bookRecord database.Book
+ var noteRecord database.Note
+ var userRecord database.User
+ var bookCount, noteCount int
+ testutils.MustExec(t, db.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, db.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, db.Where("uuid = ?", note.UUID).First(¬eRecord), "finding note")
+ testutils.MustExec(t, db.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
+ testutils.MustExec(t, db.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ assert.Equalf(t, bookCount, 1, "book count mismatch")
+ assert.Equalf(t, noteCount, 1, "note count mismatch")
+
+ assert.Equal(t, noteRecord.UUID, note.UUID, "note uuid mismatch for test case")
+ assert.Equal(t, noteRecord.Body, "", "note content mismatch for test case")
+ assert.Equal(t, noteRecord.Deleted, true, "note deleted mismatch for test case")
+ assert.Equal(t, noteRecord.BookUUID, note.BookUUID, "note book_uuid mismatch for test case")
+ assert.Equal(t, noteRecord.UserID, note.UserID, "note user_id mismatch for test case")
+ assert.Equal(t, noteRecord.USN, tc.expectedUSN, "note usn mismatch for test case")
+
+ assert.Equal(t, userRecord.MaxUSN, tc.expectedMaxUSN, "user max_usn mismatch for test case")
+ })
+ }
+}
diff --git a/pkg/server/api/handlers/v1_sync.go b/pkg/server/api/handlers/v3_sync.go
similarity index 96%
rename from pkg/server/api/handlers/v1_sync.go
rename to pkg/server/api/handlers/v3_sync.go
index c59eec1a..a6a60e94 100644
--- a/pkg/server/api/handlers/v1_sync.go
+++ b/pkg/server/api/handlers/v3_sync.go
@@ -33,7 +33,7 @@ import (
)
// fullSyncBefore is the system-wide timestamp that represents the point in time
-// before which clients much perform a full-sync rather than incremental sync.
+// before which clients must perform a full-sync rather than incremental sync.
const fullSyncBefore = 0
// SyncFragment contains a piece of information about the server's state.
@@ -303,10 +303,3 @@ func (a *App) GetSyncState(w http.ResponseWriter, r *http.Request) {
respondJSON(w, response)
}
-
-// Sync is a deprecated endpoint that used to support cli versions below v0.5.0
-// using transaction-based replication
-func (a *App) Sync(w http.ResponseWriter, r *http.Request) {
- http.Error(w, "Not supported. Please upgrade your client.", http.StatusGone)
- return
-}
diff --git a/pkg/server/api/handlers/v1_sync_test.go b/pkg/server/api/handlers/v3_sync_test.go
similarity index 100%
rename from pkg/server/api/handlers/v1_sync_test.go
rename to pkg/server/api/handlers/v3_sync_test.go
diff --git a/pkg/server/api/operations/books.go b/pkg/server/api/operations/books.go
index cf193f6c..874c4728 100644
--- a/pkg/server/api/operations/books.go
+++ b/pkg/server/api/operations/books.go
@@ -43,7 +43,7 @@ func CreateBook(user database.User, clock clock.Clock, name string) (database.Bo
Label: name,
AddedOn: clock.Now().UnixNano(),
USN: nextUSN,
- Encrypted: true,
+ Encrypted: false,
}
if err := tx.Create(&book).Error; err != nil {
tx.Rollback()
@@ -97,7 +97,7 @@ func UpdateBook(tx *gorm.DB, c clock.Clock, user database.User, book database.Bo
book.EditedOn = c.Now().UnixNano()
book.Deleted = false
// TODO: remove after all users have been migrated
- book.Encrypted = true
+ book.Encrypted = false
if err := tx.Save(&book).Error; err != nil {
return book, errors.Wrap(err, "updating the book")
diff --git a/pkg/server/api/operations/notes.go b/pkg/server/api/operations/notes.go
index 56ee99e0..8d915c74 100644
--- a/pkg/server/api/operations/notes.go
+++ b/pkg/server/api/operations/notes.go
@@ -61,7 +61,7 @@ func CreateNote(user database.User, clock clock.Clock, bookUUID, content string,
USN: nextUSN,
Body: content,
Public: public,
- Encrypted: true,
+ Encrypted: false,
}
if err := tx.Create(¬e).Error; err != nil {
tx.Rollback()
@@ -91,7 +91,7 @@ func UpdateNote(tx *gorm.DB, user database.User, clock clock.Clock, note databas
note.EditedOn = clock.Now().UnixNano()
note.Deleted = false
// TODO: remove after all users are migrated
- note.Encrypted = true
+ note.Encrypted = false
if err := tx.Save(¬e).Error; err != nil {
return note, errors.Wrap(err, "editing note")
diff --git a/pkg/server/api/operations/sessions.go b/pkg/server/api/operations/sessions.go
index d9fb6cb6..ea261db5 100644
--- a/pkg/server/api/operations/sessions.go
+++ b/pkg/server/api/operations/sessions.go
@@ -21,10 +21,10 @@ package operations
import (
"time"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
"github.com/dnote/dnote/pkg/server/api/crypt"
"github.com/dnote/dnote/pkg/server/database"
+ "github.com/jinzhu/gorm"
+ "github.com/pkg/errors"
)
// CreateSession returns a new session for the user of the given id
@@ -60,7 +60,7 @@ func DeleteUserSessions(db *gorm.DB, userID int) error {
// DeleteSession deletes the session that match the given info
func DeleteSession(db *gorm.DB, sessionKey string) error {
- if err := db.Where("key = ?", sessionKey).Delete(&database.Session{}).Error; err != nil {
+ if err := db.Debug().Where("key = ?", sessionKey).Delete(&database.Session{}).Error; err != nil {
return errors.Wrap(err, "deleting the session")
}
diff --git a/pkg/server/api/operations/users.go b/pkg/server/api/operations/users.go
index 486bfd9d..20b2f12f 100644
--- a/pkg/server/api/operations/users.go
+++ b/pkg/server/api/operations/users.go
@@ -25,6 +25,7 @@ import (
"github.com/dnote/dnote/pkg/server/database"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
+ "golang.org/x/crypto/bcrypt"
)
func generateResetToken() (string, error) {
@@ -85,65 +86,45 @@ func createEmailPreference(user database.User, tx *gorm.DB) error {
}
// CreateUser creates a user
-func CreateUser(tx *gorm.DB, email, authKey, cipherKeyEnc string, iteration int) (database.User, error) {
- salt, err := crypt.GetRandomStr(16)
+func CreateUser(email, password string) (database.User, error) {
+ db := database.DBConn
+ tx := db.Begin()
+
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
- return database.User{}, errors.Wrap(err, "generating salt")
+ tx.Rollback()
+ return database.User{}, errors.Wrap(err, "hashing password")
}
user := database.User{}
if err = tx.Save(&user).Error; err != nil {
+ tx.Rollback()
return database.User{}, errors.Wrap(err, "saving user")
}
account := database.Account{
- // TODO: email should not be nullable.
- Email: database.ToNullString(email),
- UserID: user.ID,
- AuthKeyHash: crypt.HashAuthKey(authKey, salt, crypt.ServerKDFIteration),
- Salt: salt,
- ClientKDFIteration: iteration,
- ServerKDFIteration: crypt.ServerKDFIteration,
- CipherKeyEnc: cipherKeyEnc,
+ Email: database.ToNullString(email),
+ Password: database.ToNullString(string(hashedPassword)),
+ UserID: user.ID,
}
if err = tx.Save(&account).Error; err != nil {
+ tx.Rollback()
return database.User{}, errors.Wrap(err, "saving account")
}
if err := createEmailVerificaitonToken(user, tx); err != nil {
+ tx.Rollback()
return database.User{}, errors.Wrap(err, "creating email verificaiton token")
}
if err := createEmailPreference(user, tx); err != nil {
+ tx.Rollback()
return database.User{}, errors.Wrap(err, "creating email preference")
}
if err := TouchLastLoginAt(user, tx); err != nil {
+ tx.Rollback()
return database.User{}, errors.Wrap(err, "updating last login")
}
+
+ tx.Commit()
+
return user, nil
}
-
-// LegacyRegisterUser migrates the given user to the encrypted user
-func LegacyRegisterUser(tx *gorm.DB, userID int, email, authKey string, cipherKeyEnc string, iteration int) error {
- salt, err := crypt.GetRandomStr(16)
- if err != nil {
- return errors.Wrap(err, "generating salt")
- }
-
- var account database.Account
- if err := tx.Where("user_id = ?", userID).First(&account).Error; err != nil {
- return errors.Wrap(err, "finding account")
- }
-
- account.Email = database.ToNullString(email)
- account.AuthKeyHash = crypt.HashAuthKey(authKey, salt, crypt.ServerKDFIteration)
- account.Salt = salt
- account.ClientKDFIteration = iteration
- account.ServerKDFIteration = crypt.ServerKDFIteration
- account.CipherKeyEnc = cipherKeyEnc
- account.Password = database.ToNullString("")
-
- if err = tx.Save(&account).Error; err != nil {
- return errors.Wrap(err, "saving account")
- }
-
- return nil
-}
diff --git a/pkg/server/api/presenters/presenters.go b/pkg/server/api/presenters/presenters.go
index b1eec729..a1f7670f 100644
--- a/pkg/server/api/presenters/presenters.go
+++ b/pkg/server/api/presenters/presenters.go
@@ -24,9 +24,9 @@ import (
"github.com/dnote/dnote/pkg/server/database"
)
-// formatTs rounds up the given timestamp to the microsecond
+// FormatTS rounds up the given timestamp to the microsecond
// so as to make the times in the responses consistent
-func formatTs(ts time.Time) time.Time {
+func FormatTS(ts time.Time) time.Time {
return ts.UTC().Round(time.Microsecond)
}
@@ -44,8 +44,8 @@ func PresentBook(book database.Book) Book {
return Book{
UUID: book.UUID,
USN: book.USN,
- CreatedAt: formatTs(book.CreatedAt),
- UpdatedAt: formatTs(book.UpdatedAt),
+ CreatedAt: FormatTS(book.CreatedAt),
+ UpdatedAt: FormatTS(book.UpdatedAt),
Label: book.Label,
}
}
@@ -84,14 +84,15 @@ type NoteBook struct {
// NoteUser is a nested book for PresentNotesResult
type NoteUser struct {
Name string `json:"name"`
+ UUID string `json:"uuid"`
}
// PresentNote presents note
func PresentNote(note database.Note) Note {
ret := Note{
UUID: note.UUID,
- CreatedAt: formatTs(note.CreatedAt),
- UpdatedAt: formatTs(note.UpdatedAt),
+ CreatedAt: FormatTS(note.CreatedAt),
+ UpdatedAt: FormatTS(note.UpdatedAt),
Body: note.Body,
AddedOn: note.AddedOn,
Public: note.Public,
@@ -102,6 +103,7 @@ func PresentNote(note database.Note) Note {
},
User: NoteUser{
Name: note.User.Name,
+ UUID: note.User.UUID,
},
}
@@ -154,3 +156,21 @@ func PresentDigest(digest database.Digest) Digest {
return ret
}
+
+// EmailPreference is a presented email digest
+type EmailPreference struct {
+ DigestWeekly bool `json:"digest_weekly"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// PresentEmailPreference presents a digest
+func PresentEmailPreference(p database.EmailPreference) EmailPreference {
+ ret := EmailPreference{
+ DigestWeekly: p.DigestWeekly,
+ CreatedAt: FormatTS(p.CreatedAt),
+ UpdatedAt: FormatTS(p.UpdatedAt),
+ }
+
+ return ret
+}
diff --git a/pkg/server/api/scripts/test.sh b/pkg/server/api/scripts/test.sh
index b4f09257..8029bba8 100755
--- a/pkg/server/api/scripts/test.sh
+++ b/pkg/server/api/scripts/test.sh
@@ -4,5 +4,5 @@
set -eux
pushd "$GOPATH"/src/github.com/dnote/dnote/pkg/server/api
-go test ./handlers/... ./operations/... -cover -p 1
+go test ./... -cover -p 1
popd
diff --git a/pkg/server/database/database.go b/pkg/server/database/database.go
index 6414e413..b3f31b36 100644
--- a/pkg/server/database/database.go
+++ b/pkg/server/database/database.go
@@ -101,6 +101,8 @@ var (
)
const (
+ // TokenTypeResetPassword is a type of a token for reseting password
+ TokenTypeResetPassword = "reset_password"
// TokenTypeEmailVerification is a type of a token for verifying email
TokenTypeEmailVerification = "email_verification"
// TokenTypeEmailPreference is a type of a token for updating email preference
diff --git a/pkg/server/database/migrations/20190819115834-full-text-search.sql b/pkg/server/database/migrations/20190819115834-full-text-search.sql
new file mode 100644
index 00000000..b3d884e9
--- /dev/null
+++ b/pkg/server/database/migrations/20190819115834-full-text-search.sql
@@ -0,0 +1,41 @@
+
+-- +migrate Up
+
+-- Configure full text search
+CREATE TEXT SEARCH DICTIONARY english_nostop (
+ Template = snowball,
+ Language = english
+);
+
+CREATE TEXT SEARCH CONFIGURATION public.english_nostop ( COPY = pg_catalog.english );
+
+ALTER TEXT SEARCH CONFIGURATION public.english_nostop
+ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, hword, hword_part, word WITH english_nostop;
+
+
+-- Create a trigger
+-- +migrate StatementBegin
+CREATE OR REPLACE FUNCTION note_tsv_trigger() RETURNS trigger AS $$
+begin
+ new.tsv := setweight(to_tsvector('english_nostop', new.body), 'A');
+ return new;
+end
+$$ LANGUAGE plpgsql;
+
+DROP TRIGGER IF EXISTS tsvectorupdate ON notes;
+CREATE TRIGGER tsvectorupdate
+BEFORE INSERT OR UPDATE ON notes
+FOR EACH ROW EXECUTE PROCEDURE note_tsv_trigger();
+-- +migrate StatementEnd
+
+-- index tsv
+CREATE INDEX IF NOT EXISTS idx_notes_tsv
+ON notes
+USING gin(tsv);
+
+-- initialize tsv
+UPDATE notes
+SET tsv = setweight(to_tsvector('english_nostop', notes.body), 'A')
+WHERE notes.encrypted = false;
+
+-- +migrate Down
diff --git a/pkg/server/database/models.go b/pkg/server/database/models.go
index f8ff0ac5..2937ce1e 100644
--- a/pkg/server/database/models.go
+++ b/pkg/server/database/models.go
@@ -46,11 +46,11 @@ type Book struct {
// Note is a model for a note
type Note struct {
Model
+ UUID string `json:"uuid" gorm:"index;type:uuid;default:uuid_generate_v4()"`
Book Book `json:"book" gorm:"foreignkey:BookUUID"`
User User `json:"user"`
UserID int `json:"user_id" gorm:"index"`
BookUUID string `json:"book_uuid" gorm:"index;type:uuid"`
- UUID string `json:"uuid" gorm:"index;type:uuid;default:uuid_generate_v4()"`
Body string `json:"content"`
AddedOn int64 `json:"added_on"`
EditedOn int64 `json:"edited_on"`
@@ -64,16 +64,16 @@ type Note struct {
// User is a model for a user
type User struct {
Model
- APIKey string `json:"-" gorm:"index"`
- Name string `json:"name"`
+ UUID string `json:"uuid" gorm:"type:uuid;index;default:uuid_generate_v4()"`
StripeCustomerID string `json:"-"`
BillingCountry string `json:"-"`
- Cloud bool `json:"-" gorm:"default:false"`
- IsAdmin bool `json:"-" gorm:"default:false"`
Account Account
LastLoginAt *time.Time `json:"-"`
MaxUSN int `json:"-" gorm:"default:0"`
- Encrypted bool `json:"encrypted" gorm:"default:False"`
+ Cloud bool `json:"-" gorm:"default:false"`
+ APIKey string `json:"-" gorm:"index"` // Deprecated
+ Name string `json:"name"` // Deprecated
+ Encrypted bool `json:"encrypted" gorm:"default:false"` // Deprecated
}
// Account is a model for an account
diff --git a/pkg/server/job/digest.go b/pkg/server/job/digest.go
index f03d0b64..a6a3e2e4 100644
--- a/pkg/server/job/digest.go
+++ b/pkg/server/job/digest.go
@@ -58,6 +58,20 @@ func MakeDigest(user database.User, emailAddr string) (*mailer.Email, error) {
return nil, errors.Wrap(err, "Failed to get notes with threshold 3")
}
+ noteInfos := []mailer.DigestNoteInfo{}
+ for _, note := range stage1 {
+ info := mailer.NewNoteInfo(note, 1)
+ noteInfos = append(noteInfos, info)
+ }
+ for _, note := range stage2 {
+ info := mailer.NewNoteInfo(note, 2)
+ noteInfos = append(noteInfos, info)
+ }
+ for _, note := range stage3 {
+ info := mailer.NewNoteInfo(note, 3)
+ noteInfos = append(noteInfos, info)
+ }
+
notes := append(append(stage1, stage2...), stage3...)
digest := database.Digest{
UserID: user.ID,
@@ -78,13 +92,13 @@ func MakeDigest(user database.User, emailAddr string) (*mailer.Email, error) {
tmplData := mailer.DigestTmplData{
Subject: subject,
- DigestUUID: digest.UUID,
+ NoteInfo: noteInfos,
ActiveBookCount: bookCount,
ActiveNoteCount: len(notes),
EmailSessionToken: tok.Value,
}
- email := mailer.NewEmail("notebot@dnote.io", []string{emailAddr}, subject)
+ email := mailer.NewEmail("notebot@getdnote.com", []string{emailAddr}, subject)
if err := email.ParseTemplate(mailer.EmailTypeWeeklyDigest, tmplData); err != nil {
return nil, err
}
diff --git a/pkg/server/mailer/mailer.go b/pkg/server/mailer/mailer.go
index ce448003..8c1f286d 100644
--- a/pkg/server/mailer/mailer.go
+++ b/pkg/server/mailer/mailer.go
@@ -43,6 +43,8 @@ type Email struct {
var (
// T is a map of templates
T = map[string]*template.Template{}
+ // EmailTypeResetPassword represents a reset password email
+ EmailTypeResetPassword = "reset_password"
// EmailTypeWeeklyDigest represents a weekly digest email
EmailTypeWeeklyDigest = "weekly_digest"
// EmailTypeEmailVerification represents an email verification email
@@ -97,15 +99,20 @@ func InitTemplates(srcDir *string) {
weeklyDigestTmpl, err := initTemplate(box, EmailTypeWeeklyDigest)
if err != nil {
- panic(errors.Wrap(err, "initializing template"))
+ panic(errors.Wrap(err, "initializing weekly digest template"))
}
emailVerificationTmpl, err := initTemplate(box, EmailTypeEmailVerification)
if err != nil {
- panic(errors.Wrap(err, "initializing template"))
+ panic(errors.Wrap(err, "initializing email verification template"))
+ }
+ passwowrdResetTmpl, err := initTemplate(box, EmailTypeResetPassword)
+ if err != nil {
+ panic(errors.Wrap(err, "initializing password reset template"))
}
T[EmailTypeWeeklyDigest] = weeklyDigestTmpl
T[EmailTypeEmailVerification] = emailVerificationTmpl
+ T[EmailTypeResetPassword] = passwowrdResetTmpl
}
// NewEmail returns a pointer to an Email struct with the given data
diff --git a/pkg/server/mailer/templates/main.go b/pkg/server/mailer/templates/main.go
index 81c4d6b1..43459976 100644
--- a/pkg/server/mailer/templates/main.go
+++ b/pkg/server/mailer/templates/main.go
@@ -40,7 +40,7 @@ func weeklyDigestHandler(w http.ResponseWriter, r *http.Request) {
return
}
- email, err := job.MakeDigest(user, "sung@dnote.io")
+ email, err := job.MakeDigest(user, "sung@getdnote.com")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -58,7 +58,7 @@ func emailVerificationHandler(w http.ResponseWriter, r *http.Request) {
"Verify your email",
"testToken",
}
- email := mailer.NewEmail("noreply@dnote.io", []string{"sung@dnote.io"}, "Reset your password")
+ email := mailer.NewEmail("noreply@getdnote.com", []string{"sung@getdnote.com"}, "Reset your password")
err := email.ParseTemplate(mailer.EmailTypeEmailVerification, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/pkg/server/mailer/templates/src/email_verification.html b/pkg/server/mailer/templates/src/email_verification.html
index 1ec064a4..c25bd174 100644
--- a/pkg/server/mailer/templates/src/email_verification.html
+++ b/pkg/server/mailer/templates/src/email_verification.html
@@ -318,7 +318,7 @@
|
- Verify email
+ Verify email
|
@@ -332,7 +332,7 @@
|
- Alternatively you can manually go to the following URL: https://dnote.io/app/verify-email/{{ .Token }}
+ Alternatively you can manually go to the following URL: https://app.getdnote.com/verify-email/{{ .Token }}
|
diff --git a/pkg/server/mailer/templates/src/header.html b/pkg/server/mailer/templates/src/header.html
index 0a8cf79e..39ab5c0a 100644
--- a/pkg/server/mailer/templates/src/header.html
+++ b/pkg/server/mailer/templates/src/header.html
@@ -22,8 +22,8 @@
diff --git a/pkg/server/mailer/templates/src/reset_password.html b/pkg/server/mailer/templates/src/reset_password.html
index 4510e04b..7b56893c 100644
--- a/pkg/server/mailer/templates/src/reset_password.html
+++ b/pkg/server/mailer/templates/src/reset_password.html
@@ -319,7 +319,7 @@
|
- Reset Password
+ Reset Password
|
diff --git a/pkg/server/mailer/templates/src/weekly_digest.html b/pkg/server/mailer/templates/src/weekly_digest.html
index baa2d2db..7393d82d 100644
--- a/pkg/server/mailer/templates/src/weekly_digest.html
+++ b/pkg/server/mailer/templates/src/weekly_digest.html
@@ -276,6 +276,70 @@
color: gray;
font-size: 12px;
}
+ .icon {
+ margin-right: 1px;
+ vertical-align: middle;
+ }
+ .meta-label {
+ vertical-align: middle;
+ font-size: 12px;
+ padding-right: 5px;
+ }
+ .note {
+ padding: 6px 9px;
+ border-radius: 2px;
+ }
+ .timeago {
+ font-weight: 600;
+ }
+ .note.stage1 {
+ border: 1px solid #ffe3b9;
+ }
+ .note.stage2 {
+ border: 2px solid #ffc107;
+ }
+ .note.stage3 {
+ border: 2px solid #6d983a;
+
+ }
+ .stage1 .timeago {
+ color: #ffe3b9;
+ }
+ .stage2 .timeago {
+ color: #FF9800;
+ }
+ .stage3 .timeago {
+ color: green;
+ }
+ .note-meta {
+ color: #585858;
+ text-align: right;
+ }
+ .note-content {
+ font-size: 15px;
+ }
+ .spacer td {
+ padding-top: 7px;
+ }
+ .text-center {
+ text-align: center;
+ }
+ .intro {
+ text-align: center;
+ }
+ .intro p {
+ margin-bottom: 7px;
+ }
+
+ .outro {
+ text-align: center;
+ }
+ .outro p {
+ margin-bottom: 7px;
+ }
+ .notes-container {
+ padding: 12px 0;
+ }
@@ -300,48 +364,45 @@
This is your weekly Dnote digest, featuring {{ .ActiveNoteCount }} notes from {{ .ActiveBookCount }} books.
-
- |
- Please navigate to the link below to view the digest.
- |
-
-
-
+
+
-
- |
-
- |
-
+ {{ range .NoteInfo }}
+
+
+
+
+
+ |
+ {{ .Content }}
+ |
+
+
+ |
+ Open
+ |
+
+
+
+ {{ .BookLabel }} {{ .TimeAgo }}
+ |
+
+
+
+ |
+
+
+ |
+
+ {{ end }}
|
-
- |
- — Dnote
- |
-
-
-
- |
- Your data is safe and private. Dnote has zero knowledge about contents of your notes. Only you can decrypt them.
- |
-
-
-
|
@@ -364,7 +425,7 @@
|
- Change email frequency
+ Change email frequency
|
diff --git a/pkg/server/mailer/types.go b/pkg/server/mailer/types.go
index 45975051..d677579a 100644
--- a/pkg/server/mailer/types.go
+++ b/pkg/server/mailer/types.go
@@ -18,11 +18,40 @@
package mailer
+import (
+ "time"
+
+ "github.com/dnote/dnote/pkg/server/database"
+ "github.com/justincampbell/timeago"
+)
+
+// DigestNoteInfo contains note information for digest emails
+type DigestNoteInfo struct {
+ UUID string
+ Content string
+ BookLabel string
+ TimeAgo string
+ Stage int
+}
+
// DigestTmplData is a template data for digest emails
type DigestTmplData struct {
Subject string
- DigestUUID string
+ NoteInfo []DigestNoteInfo
ActiveBookCount int
ActiveNoteCount int
EmailSessionToken string
}
+
+// NewNoteInfo returns a new NoteInfo
+func NewNoteInfo(note database.Note, stage int) DigestNoteInfo {
+ tm := time.Unix(0, int64(note.AddedOn))
+
+ return DigestNoteInfo{
+ UUID: note.UUID,
+ Content: note.Body,
+ BookLabel: note.Book.Label,
+ TimeAgo: timeago.FromTime(tm),
+ Stage: stage,
+ }
+}
diff --git a/pkg/server/main.go b/pkg/server/main.go
index 3d235a81..b870b0ba 100644
--- a/pkg/server/main.go
+++ b/pkg/server/main.go
@@ -24,7 +24,6 @@ import (
"log"
"net/http"
"os"
- "strings"
"github.com/dnote/dnote/pkg/clock"
"github.com/dnote/dnote/pkg/server/api/handlers"
@@ -40,27 +39,53 @@ import (
var versionTag = "master"
var port = flag.String("port", "3000", "port to connect to")
+var rootBox *packr.Box
+
func init() {
+ rootBox = packr.New("root", "../../web/public")
}
-func getAppHandler() http.HandlerFunc {
- box := packr.New("web", "../../web/public")
-
- fs := http.FileServer(box)
- appShell, err := box.Find("index.html")
+func mustFind(box *packr.Box, path string) []byte {
+ b, err := rootBox.Find(path)
if err != nil {
- panic(errors.Wrap(err, "getting index.html content"))
+ panic(errors.Wrapf(err, "getting file content for %s", path))
}
- return func(w http.ResponseWriter, r *http.Request) {
- parts := strings.Split(r.URL.Path, "/")
- if len(parts) >= 2 && parts[1] == "dist" {
- fs.ServeHTTP(w, r)
- return
- }
+ return b
+}
- // All other requests should serve the index.html file
- w.Write(appShell)
+func getStaticHandler() http.Handler {
+ box := packr.New("static", "../../web/public/static")
+
+ return http.StripPrefix("/static/", http.FileServer(box))
+}
+
+// getRootHandler returns an HTTP handler that serves the app shell
+func getRootHandler() http.HandlerFunc {
+ b := mustFind(rootBox, "index.html")
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Write(b)
+ }
+}
+
+func getRobotsHandler() http.HandlerFunc {
+ b := mustFind(rootBox, "robots.txt")
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Write(b)
+ }
+}
+
+func getSWHandler() http.HandlerFunc {
+ b := mustFind(rootBox, "service-worker.js")
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Content-Type", "application/javascript")
+ w.Write(b)
}
}
@@ -73,7 +98,12 @@ func initServer() *mux.Router {
})
srv.PathPrefix("/api").Handler(http.StripPrefix("/api", apiRouter))
- srv.PathPrefix("/").HandlerFunc(getAppHandler())
+ srv.PathPrefix("/static").Handler(getStaticHandler())
+ srv.Handle("/service-worker.js", getSWHandler())
+ srv.Handle("/robots.txt", getRobotsHandler())
+
+ // For all other requests, serve the index.html file
+ srv.PathPrefix("/").Handler(getRootHandler())
return srv
}
diff --git a/pkg/server/testutils/config.go b/pkg/server/testutils/config.go
new file mode 100644
index 00000000..74d98a79
--- /dev/null
+++ b/pkg/server/testutils/config.go
@@ -0,0 +1,27 @@
+package testutils
+
+import (
+ "os"
+ "path/filepath"
+)
+
+// ProjectPath is the path of the proprietary test suite relative to the "GOPATH"
+var ProjectPath string
+
+// CLIPath is the path to the CLI project
+var CLIPath string
+
+// ServerPath is the path to the Dnote server project
+var ServerPath string
+
+func init() {
+ goPath := os.Getenv("GOPATH")
+ if goPath == "" {
+ panic("GOPATH is not set up")
+ }
+
+ // Populate paths
+ ProjectPath = filepath.Join(goPath, "src/gitlab.com/monomax/dnote-infra")
+ CLIPath = filepath.Join(goPath, "src/github.com/dnote/dnote/pkg/cli")
+ ServerPath = filepath.Join(goPath, "src/github.com/dnote/dnote/pkg/server")
+}
diff --git a/pkg/server/testutils/main.go b/pkg/server/testutils/main.go
index 097ab657..48b7f033 100644
--- a/pkg/server/testutils/main.go
+++ b/pkg/server/testutils/main.go
@@ -30,10 +30,10 @@ import (
"time"
"github.com/dnote/dnote/pkg/server/database"
- "github.com/stripe/stripe-go"
-
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
+ "github.com/stripe/stripe-go"
+ "golang.org/x/crypto/bcrypt"
)
// InitTestDB establishes connection pool with the test database specified by
@@ -68,7 +68,31 @@ func SetupUserData() database.User {
}
// SetupAccountData creates and returns a new account for the user
-func SetupAccountData(user database.User, email string) database.Account {
+func SetupAccountData(user database.User, email, password string) database.Account {
+ db := database.DBConn
+
+ account := database.Account{
+ UserID: user.ID,
+ }
+ if email != "" {
+ account.Email = database.ToNullString(email)
+ }
+
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ panic(errors.Wrap(err, "Failed to hash password"))
+ }
+ account.Password = database.ToNullString(string(hashedPassword))
+
+ if err := db.Save(&account).Error; err != nil {
+ panic(errors.Wrap(err, "Failed to prepare account"))
+ }
+
+ return account
+}
+
+// SetupClassicAccountData creates and returns a new account for the user
+func SetupClassicAccountData(user database.User, email string) database.Account {
db := database.DBConn
// email: alice@example.com
diff --git a/web/.eslintrc b/web/.eslintrc
index bbaaf9f7..0f9450f9 100644
--- a/web/.eslintrc
+++ b/web/.eslintrc
@@ -4,8 +4,9 @@
"node": true,
"mocha": true
},
- "parser": "babel-eslint",
+ "parser": "@typescript-eslint/parser",
"rules": {
+ "camelcase": 0,
"strict": 0,
"react/no-multi-comp": 0,
"import/default": 0,
@@ -24,6 +25,7 @@
"react/jsx-filename-extension": 0,
"react/prefer-stateless-function": 0,
"jsx-a11y/anchor-is-valid": 0,
+ "jsx-a11y/tabindex-no-positive": 0,
"no-mixed-operators": 0,
"no-plusplus": 0,
"no-underscore-dangle": 0,
@@ -34,10 +36,13 @@
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/jsx-wrap-multilines": ["error", {"declaration": false, "assignment": false}],
- "react/jsx-one-expression-per-line": 0
+ "react/jsx-one-expression-per-line": 0,
+ "@typescript-eslint/no-unused-vars": 1,
+ "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*_test.ts"]}],
+ "lines-between-class-members": 0
},
"plugins": [
- "react", "react-hooks", "import", "prettier"
+ "react", "react-hooks", "import", "prettier", "@typescript-eslint"
],
"settings": {
"import/parser": "babel-eslint",
@@ -54,6 +59,8 @@
"__BASE_URL__": true,
"__BASE_NAME__": true,
"__STRIPE_PUBLIC_KEY__": true,
+ "__ROOT_URL__": true,
+ "__CDN_URL__": true,
"socket": true,
"webpackIsomorphicTools": true,
"StripeCheckout": true
diff --git a/web/assets/index.html b/web/assets/index.html
new file mode 100644
index 00000000..4c7658e5
--- /dev/null
+++ b/web/assets/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+ Dnote
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/assets/robots.txt b/web/assets/robots.txt
new file mode 100644
index 00000000..1f53798b
--- /dev/null
+++ b/web/assets/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/web/assets/service-worker.js b/web/assets/service-worker.js
new file mode 100644
index 00000000..4b48b338
--- /dev/null
+++ b/web/assets/service-worker.js
@@ -0,0 +1,30 @@
+/* eslint-disable no-restricted-globals, func-names, no-var, prefer-template */
+
+self.addEventListener('install', function(event) {
+ var offlineRequest = new Request('static/offline.html');
+
+ event.waitUntil(
+ fetch(offlineRequest).then(function(response) {
+ return caches.open('offline').then(function(cache) {
+ console.log('cached offline page', response.url);
+ return cache.put(offlineRequest, response);
+ });
+ })
+ );
+});
+
+self.addEventListener('fetch', function(event) {
+ var request = event.request;
+ if (request.method === 'GET') {
+ event.respondWith(
+ fetch(request).catch(function(error) {
+ console.error(
+ 'onfetch Failed. Serving cached offline fallback ' + error
+ );
+ return caches.open('offline').then(function(cache) {
+ return cache.match('offline.html');
+ });
+ })
+ );
+ }
+});
diff --git a/web/static/404.html b/web/assets/static/404.html
similarity index 76%
rename from web/static/404.html
rename to web/assets/static/404.html
index 84fff815..27cfcfbc 100644
--- a/web/static/404.html
+++ b/web/assets/static/404.html
@@ -5,10 +5,8 @@
Page Not Found | Dnote
-
+
+
+
+ You are offline
+
+ Please check you connection and try again.
+
+
+
+