diff options
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/gulpfile.js | 7 | ||||
| -rw-r--r-- | gui/package-lock.json | 1210 | ||||
| -rw-r--r-- | gui/package.json | 6 | ||||
| -rwxr-xr-x | gui/scripts/build-proto.sh | 33 | ||||
| -rw-r--r-- | gui/src/main/daemon-rpc.ts | 1543 | ||||
| -rw-r--r-- | gui/src/main/index.ts | 49 | ||||
| -rw-r--r-- | gui/src/renderer/components/RedeemVoucher.tsx | 2 | ||||
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 18 | ||||
| -rw-r--r-- | gui/tasks/scripts.js | 9 |
9 files changed, 2282 insertions, 595 deletions
diff --git a/gui/gulpfile.js b/gui/gulpfile.js index f203fc16d0..6a7226c1aa 100644 --- a/gui/gulpfile.js +++ b/gui/gulpfile.js @@ -1,4 +1,4 @@ -const { task, series } = require('gulp'); +const { task, series, parallel } = require('gulp'); const rimraf = require('rimraf'); const scripts = require('./tasks/scripts'); @@ -9,8 +9,9 @@ const dist = require('./tasks/distribution'); task('clean', function (done) { rimraf('./build', done); }); -task('build', series('clean', assets.copyAll, scripts.build)); -task('develop', series('clean', watch.start)); +task('build-proto', scripts.buildProto); +task('build', series('clean', parallel(assets.copyAll, scripts.buildProto), scripts.build)); +task('develop', series('clean', scripts.buildProto, watch.start)); task('pack-win', series('build', dist.packWin)); task('pack-linux', series('build', dist.packLinux)); task('pack-mac', series('build', dist.packMac)); diff --git a/gui/package-lock.json b/gui/package-lock.json index c3b781bf69..57840f46f4 100644 --- a/gui/package-lock.json +++ b/gui/package-lock.json @@ -397,6 +397,21 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "@grpc/grpc-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.2.tgz", + "integrity": "sha512-k2u86Bkm/3xrjUaSWeIyzXScBt/cC8uE7BznR0cpueQi11R33W6qfJdMrkrsmSHirp5likR55JSXUrcWG6ybHA==", + "requires": { + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -559,6 +574,12 @@ "@types/node": "*" } }, + "@types/google-protobuf": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.7.2.tgz", + "integrity": "sha512-ifFemzjNchFBCtHS6bZNhSZCBu7tbtOe0e8qY0z2J4HtFXmPJjm6fXSaQsTG7yhShBEZtt2oP/bkwu5k+emlkQ==", + "dev": true + }, "@types/history": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz", @@ -872,8 +893,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "optional": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -958,6 +978,105 @@ } } }, + "ansi-bgblack": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgblack/-/ansi-bgblack-0.1.1.tgz", + "integrity": "sha1-poulAHiHcBtqr74/oNrf36juPKI=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgblue": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgblue/-/ansi-bgblue-0.1.1.tgz", + "integrity": "sha1-Z73ATtybm1J4lp2hlt6j11yMNhM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgcyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgcyan/-/ansi-bgcyan-0.1.1.tgz", + "integrity": "sha1-WEiUJWAL3p9VBwaN2Wnr/bUP52g=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bggreen": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bggreen/-/ansi-bggreen-0.1.1.tgz", + "integrity": "sha1-TjGRJIUplD9DIelr8THRwTgWr0k=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgmagenta": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgmagenta/-/ansi-bgmagenta-0.1.1.tgz", + "integrity": "sha1-myhDLAduqpmUGGcqPvvhk5HCx6E=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgred": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgred/-/ansi-bgred-0.1.1.tgz", + "integrity": "sha1-p2+Sg4OCukMpCmwXeEJPmE1vEEE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgwhite": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgwhite/-/ansi-bgwhite-0.1.1.tgz", + "integrity": "sha1-ZQRlE3elim7OzQMxmU5IAljhG6g=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgyellow": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgyellow/-/ansi-bgyellow-0.1.1.tgz", + "integrity": "sha1-w/4usIzUdmSAKeaHTRWgs49h1E8=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-black": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-black/-/ansi-black-0.1.1.tgz", + "integrity": "sha1-9hheiJNgslRaHsUMC/Bj/EMDJFM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-blue": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-blue/-/ansi-blue-0.1.1.tgz", + "integrity": "sha1-FbgEmQ6S/JyoxUds6PaZd3wh7b8=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bold": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bold/-/ansi-bold-0.1.1.tgz", + "integrity": "sha1-PmOVCvWswq4uZw5vZ96xFdGl9QU=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-colors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", @@ -967,6 +1086,24 @@ "ansi-wrap": "^0.1.0" } }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-dim": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-dim/-/ansi-dim-0.1.1.tgz", + "integrity": "sha1-QN5MYDqoCG2Oeoa4/5mNXDbu/Ww=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-escapes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", @@ -993,23 +1130,131 @@ "ansi-wrap": "0.1.0" } }, + "ansi-green": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-green/-/ansi-green-0.1.1.tgz", + "integrity": "sha1-il2al55FjVfEDjNYCzc5C44Q0Pc=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-grey": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-grey/-/ansi-grey-0.1.1.tgz", + "integrity": "sha1-WdmLasK6GfilF5jphT+6eDOaM8E=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-hidden": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-hidden/-/ansi-hidden-0.1.1.tgz", + "integrity": "sha1-7WpMSY0rt8uyidvyqNHcyFZ/rg8=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-inverse": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-inverse/-/ansi-inverse-0.1.1.tgz", + "integrity": "sha1-tq9Fgm/oJr+1KKbHmIV5Q1XM0mk=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-italic": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-italic/-/ansi-italic-0.1.1.tgz", + "integrity": "sha1-EEdDRj9iXBQqA2c5z4XtpoiYbyM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-magenta": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-magenta/-/ansi-magenta-0.1.1.tgz", + "integrity": "sha1-BjtboW+z8j4c/aKwfAqJ3hHkMK4=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "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=" }, + "ansi-reset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-reset/-/ansi-reset-0.1.1.tgz", + "integrity": "sha1-5+cSksPH3c1NYu9KbHwFmAkRw7c=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-strikethrough": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-strikethrough/-/ansi-strikethrough-0.1.1.tgz", + "integrity": "sha1-2Eh3FAss/wfRyT685pkE9oiF5Wg=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "ansi-underline": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-underline/-/ansi-underline-0.1.1.tgz", + "integrity": "sha1-38kg9Ml7WXfqFi34/7mIMIqqcaQ=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-white": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-white/-/ansi-white-0.1.1.tgz", + "integrity": "sha1-nHe3wZPF7pkuYBHTbsTJIbRXiUQ=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.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 }, + "ansi-yellow": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-yellow/-/ansi-yellow-0.1.1.tgz", + "integrity": "sha1-y5NW8vRscy8OMZnmEClVp32oPB0=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -1179,8 +1424,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "optional": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "archy": { "version": "1.0.0", @@ -1192,7 +1436,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -1585,6 +1828,15 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "autolinker": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz", + "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=", + "dev": true, + "requires": { + "gulp-header": "^1.7.1" + } + }, "axios": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", @@ -2503,8 +2755,7 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "optional": true + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "chromium-pickle-js": { "version": "0.2.0", @@ -2696,6 +2947,23 @@ "typedarray": "^0.0.6" } }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "requires": { + "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 + } + } + }, "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", @@ -2762,8 +3030,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "content-type": { "version": "1.0.4", @@ -2813,6 +3080,38 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "create-frame": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/create-frame/-/create-frame-1.0.0.tgz", + "integrity": "sha1-i5XyaR4ySbYIBEPjPQutn49pdao=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "isobject": "^3.0.0", + "lazy-cache": "^2.0.2" + }, + "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" + } + } + } + }, "cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", @@ -2932,6 +3231,26 @@ "resolve": "^1.1.10" } }, + "date.js": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz", + "integrity": "sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==", + "dev": true, + "requires": { + "debug": "~3.1.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" + } + } + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3072,8 +3391,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "optional": true + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { "version": "1.1.2", @@ -3096,8 +3414,7 @@ "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "optional": true + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "detect-node": { "version": "2.0.4", @@ -4000,6 +4317,12 @@ "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 + }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -4089,6 +4412,12 @@ "is-arrayish": "^0.2.1" } }, + "error-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/error-symbol/-/error-symbol-0.1.0.tgz", + "integrity": "sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y=", + "dev": true + }, "es-abstract": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", @@ -4853,6 +5182,23 @@ } } }, + "falsey": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/falsey/-/falsey-0.3.2.tgz", + "integrity": "sha512-lxEuefF5MBIVDmE6XeqCdM4BWk1+vYmGZtkbKZ/VFcg6uBBw6fXNEbWmxCjDdQlFc9hy450nkiWwM3VAW6G1qg==", + "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 + } + } + }, "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", @@ -5108,6 +5454,12 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", + "dev": true + }, "fs-extra": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", @@ -5123,7 +5475,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "optional": true, "requires": { "minipass": "^2.6.0" } @@ -5739,7 +6090,6 @@ "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -5763,6 +6113,42 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-object": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/get-object/-/get-object-0.2.0.tgz", + "integrity": "sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw=", + "dev": true, + "requires": { + "is-number": "^2.0.2", + "isobject": "^0.2.0" + }, + "dependencies": { + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-0.2.0.tgz", + "integrity": "sha1-o0MhkvObkQtfAsyYlIeDbscKqF4=", + "dev": true + }, + "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" + } + } + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -5993,6 +6379,11 @@ "sparkles": "^1.0.0" } }, + "google-protobuf": { + "version": "4.0.0-rc.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-4.0.0-rc.2.tgz", + "integrity": "sha512-KANe0ePi8Uk0m17xvjI1bXrE7LRflSuNczkAlHlOEeqXnSBy96tDS5beKwKOnNr0xYmlbC9XiQsj8fOLLyDp2Q==" + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -6035,6 +6426,89 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "grpc-tools": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/grpc-tools/-/grpc-tools-1.9.1.tgz", + "integrity": "sha512-t2JFMPLjxcgwVSJwFEauFaoEiO56kijxSwehQDgZNR/hrStJCH0pHGsjqJNuCOvmI9Z31pYOfgj4zeInTQWh5A==", + "dev": true, + "requires": { + "node-pre-gyp": "^0.15.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" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "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 + }, + "needle": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", + "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", + "dev": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "dev": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "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.4.2" + } + } + } + }, + "grpc_tools_node_protoc_ts": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-4.1.0.tgz", + "integrity": "sha512-+10Q2LhTalc41e+hCfP7lCLYfyDlAdntEKTn43QHE3CPafNHRu5KgqAHkDg4cUiDEJFUcKhddaYUPI5uR7c2AA==", + "dev": true, + "requires": { + "google-protobuf": "3.5.0", + "handlebars": "4.7.4", + "handlebars-helpers": "0.10.0" + }, + "dependencies": { + "google-protobuf": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.5.0.tgz", + "integrity": "sha1-uMxjx02DRXvYqakEUDyO+ya8ozk=", + "dev": true + } + } + }, "gud": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", @@ -6110,6 +6584,17 @@ } } }, + "gulp-header": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", + "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", + "dev": true, + "requires": { + "concat-with-sourcemaps": "*", + "lodash.template": "^4.4.0", + "through2": "^2.0.0" + } + }, "gulp-inject-string": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/gulp-inject-string/-/gulp-inject-string-1.1.2.tgz", @@ -6166,6 +6651,325 @@ "glogg": "^1.0.0" } }, + "handlebars": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.4.tgz", + "integrity": "sha512-Is8+SzHv8K9STNadlBVpVhxXrSXxVgTyIvhdg2Qjak1SfSZ7iEozLHdwiX1jJ9lLFkcFJxqGK5s/cI7ZX+qGkQ==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "yargs": "^15.3.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.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 + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "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 + }, + "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 + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.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 + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.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": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "handlebars-helper-create-frame": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/handlebars-helper-create-frame/-/handlebars-helper-create-frame-0.1.0.tgz", + "integrity": "sha1-iqUdEK62QI/MZgXUDXc1YohIegM=", + "dev": true, + "requires": { + "create-frame": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "handlebars-helpers": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/handlebars-helpers/-/handlebars-helpers-0.10.0.tgz", + "integrity": "sha512-QiyhQz58u/DbuV41VnfpE0nhy6YCH4vB514ajysV8SoKmP+DxU+pR+fahVyNECHj+jiwEN2VrvxD/34/yHaLUg==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-sort": "^0.1.4", + "create-frame": "^1.0.0", + "define-property": "^1.0.0", + "falsey": "^0.3.2", + "for-in": "^1.0.2", + "for-own": "^1.0.0", + "get-object": "^0.2.0", + "get-value": "^2.0.6", + "handlebars": "^4.0.11", + "handlebars-helper-create-frame": "^0.1.0", + "handlebars-utils": "^1.0.6", + "has-value": "^1.0.0", + "helper-date": "^1.0.1", + "helper-markdown": "^1.0.0", + "helper-md": "^0.2.2", + "html-tag": "^2.0.0", + "is-even": "^1.0.0", + "is-glob": "^4.0.0", + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "lazy-cache": "^2.0.2", + "logging-helpers": "^1.0.0", + "micromatch": "^3.1.4", + "relative": "^3.0.2", + "striptags": "^3.1.0", + "to-gfm-code-block": "^0.1.1", + "year": "^0.2.1" + }, + "dependencies": { + "array-sort": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-0.1.4.tgz", + "integrity": "sha512-BNcM+RXxndPxiZ2rd76k6nyQLRZr2/B/sdi8pQ+Joafr5AH279L40dfokSUTp8O+AaqYjXWhblBWa2st2nc4fQ==", + "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 + } + } + }, + "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" + } + }, + "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 + } + } + }, + "handlebars-utils": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/handlebars-utils/-/handlebars-utils-1.0.6.tgz", + "integrity": "sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw==", + "dev": true, + "requires": { + "kind-of": "^6.0.0", + "typeof-article": "^0.1.1" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6221,8 +7025,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "optional": true + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "has-value": { "version": "1.0.0", @@ -6268,6 +7071,57 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "helper-date": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/helper-date/-/helper-date-1.0.1.tgz", + "integrity": "sha512-wU3VOwwTJvGr/w5rZr3cprPHO+hIhlblTJHD6aFBrKLuNbf4lAmkawd2iK3c6NbJEvY7HAmDpqjOFSI5/+Ey2w==", + "dev": true, + "requires": { + "date.js": "^0.3.1", + "handlebars-utils": "^1.0.4", + "moment": "^2.18.1" + } + }, + "helper-markdown": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/helper-markdown/-/helper-markdown-1.0.0.tgz", + "integrity": "sha512-AnDqMS4ejkQK0MXze7pA9TM3pu01ZY+XXsES6gEE0RmCGk5/NIfvTn0NmItfyDOjRAzyo9z6X7YHbHX4PzIvOA==", + "dev": true, + "requires": { + "handlebars-utils": "^1.0.2", + "highlight.js": "^9.12.0", + "remarkable": "^1.7.1" + } + }, + "helper-md": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/helper-md/-/helper-md-0.2.2.tgz", + "integrity": "sha1-wfWdflW7riM2L9ig6XFgeuxp1B8=", + "dev": true, + "requires": { + "ent": "^2.2.0", + "extend-shallow": "^2.0.1", + "fs-exists-sync": "^0.1.0", + "remarkable": "^1.6.2" + }, + "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" + } + } + } + }, + "highlight.js": { + "version": "9.18.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", + "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==", + "dev": true + }, "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", @@ -6303,6 +7157,16 @@ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, + "html-tag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tag/-/html-tag-2.0.0.tgz", + "integrity": "sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g==", + "dev": true, + "requires": { + "is-self-closing": "^1.0.1", + "kind-of": "^6.0.0" + } + }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -6392,7 +7256,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "optional": true, "requires": { "minimatch": "^3.0.4" } @@ -6445,6 +7308,12 @@ "wrappy": "1" } }, + "info-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/info-symbol/-/info-symbol-0.1.0.tgz", + "integrity": "sha1-J4QdcoZ920JCzWEtecEGM4gcang=", + "dev": true + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -6792,6 +7661,15 @@ "integrity": "sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI=", "dev": true }, + "is-even": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-even/-/is-even-1.0.0.tgz", + "integrity": "sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY=", + "dev": true, + "requires": { + "is-odd": "^0.1.2" + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -6888,6 +7766,15 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, + "is-odd": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-0.1.2.tgz", + "integrity": "sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc=", + "dev": true, + "requires": { + "is-number": "^3.0.0" + } + }, "is-path-inside": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", @@ -6933,6 +7820,15 @@ "is-unc-path": "^1.0.0" } }, + "is-self-closing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-self-closing/-/is-self-closing-1.0.1.tgz", + "integrity": "sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg==", + "dev": true, + "requires": { + "self-closing-tags": "^1.0.1" + } + }, "is-set": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", @@ -7217,6 +8113,15 @@ "package-json": "^6.3.0" } }, + "lazy-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", + "dev": true, + "requires": { + "set-getter": "^0.1.0" + } + }, "lazy-val": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.4.tgz", @@ -7465,6 +8370,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, "lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", @@ -7494,6 +8405,35 @@ "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=", "dev": true }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "log-ok": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/log-ok/-/log-ok-0.1.1.tgz", + "integrity": "sha1-vqPdNqzQuKckDXhza1uXxlREozQ=", + "dev": true, + "requires": { + "ansi-green": "^0.1.1", + "success-symbol": "^0.1.0" + } + }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -7555,6 +8495,68 @@ } } }, + "log-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/log-utils/-/log-utils-0.2.1.tgz", + "integrity": "sha1-pMIXoN2aUFFdm5ICBgkas9TgMc8=", + "dev": true, + "requires": { + "ansi-colors": "^0.2.0", + "error-symbol": "^0.1.0", + "info-symbol": "^0.1.0", + "log-ok": "^0.1.1", + "success-symbol": "^0.1.0", + "time-stamp": "^1.0.1", + "warning-symbol": "^0.1.0" + }, + "dependencies": { + "ansi-colors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-0.2.0.tgz", + "integrity": "sha1-csMd4qDZoszQysMMyYI+6y9kNLU=", + "dev": true, + "requires": { + "ansi-bgblack": "^0.1.1", + "ansi-bgblue": "^0.1.1", + "ansi-bgcyan": "^0.1.1", + "ansi-bggreen": "^0.1.1", + "ansi-bgmagenta": "^0.1.1", + "ansi-bgred": "^0.1.1", + "ansi-bgwhite": "^0.1.1", + "ansi-bgyellow": "^0.1.1", + "ansi-black": "^0.1.1", + "ansi-blue": "^0.1.1", + "ansi-bold": "^0.1.1", + "ansi-cyan": "^0.1.1", + "ansi-dim": "^0.1.1", + "ansi-gray": "^0.1.1", + "ansi-green": "^0.1.1", + "ansi-grey": "^0.1.1", + "ansi-hidden": "^0.1.1", + "ansi-inverse": "^0.1.1", + "ansi-italic": "^0.1.1", + "ansi-magenta": "^0.1.1", + "ansi-red": "^0.1.1", + "ansi-reset": "^0.1.1", + "ansi-strikethrough": "^0.1.1", + "ansi-underline": "^0.1.1", + "ansi-white": "^0.1.1", + "ansi-yellow": "^0.1.1", + "lazy-cache": "^2.0.1" + } + } + } + }, + "logging-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/logging-helpers/-/logging-helpers-1.0.0.tgz", + "integrity": "sha512-qyIh2goLt1sOgQQrrIWuwkRjUx4NUcEqEGAcYqD8VOnOC6ItwkrVE8/tA4smGpjzyp4Svhc6RodDp9IO5ghpyA==", + "dev": true, + "requires": { + "isobject": "^3.0.0", + "log-utils": "^0.2.1" + } + }, "lolex": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", @@ -7575,24 +8577,6 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, - "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" - }, - "dependencies": { - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -7781,7 +8765,6 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7791,7 +8774,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "optional": true, "requires": { "minipass": "^2.9.0" } @@ -8351,6 +9333,12 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", "dev": true }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -8431,7 +9419,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "optional": true, "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -8474,7 +9461,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "optional": true, "requires": { "npm-normalize-package-bin": "^1.0.1" } @@ -8502,14 +9488,12 @@ "npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "optional": true + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-packlist": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "optional": true, "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1", @@ -8520,7 +9504,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -8790,8 +9773,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "optional": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "1.4.0", @@ -8811,7 +9793,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "optional": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -9246,12 +10227,6 @@ "event-stream": "=3.3.4" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -9753,6 +10728,42 @@ "rc": "^1.2.8" } }, + "relative": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz", + "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=", + "dev": true, + "requires": { + "isobject": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "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" + } + } + } + }, + "remarkable": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", + "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==", + "dev": true, + "requires": { + "argparse": "^1.0.10", + "autolinker": "~0.28.0" + } + }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -10031,6 +11042,12 @@ "object-assign": "^4.1.1" } }, + "self-closing-tags": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/self-closing-tags/-/self-closing-tags-1.0.1.tgz", + "integrity": "sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA==", + "dev": true + }, "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", @@ -10208,6 +11225,15 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-getter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", + "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", + "dev": true, + "requires": { + "to-object-path": "^0.3.0" + } + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -11150,6 +12176,12 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "striptags": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz", + "integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0=", + "dev": true + }, "styled-components": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.1.0.tgz", @@ -11182,6 +12214,12 @@ "resolved": "https://registry.npmjs.org/subscribableevent/-/subscribableevent-1.0.1.tgz", "integrity": "sha512-rFXit43+QoyWRUV/aTiYMUUJl71xmDcxED2cza3PEED75pgHTofMyPcPQrnHzyPqZh+kazEd36eydUKbOZBCpQ==" }, + "success-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz", + "integrity": "sha1-JAIuSG878c3KCUKDt2nEctO3KJc=", + "dev": true + }, "sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -11291,7 +12329,6 @@ "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -11306,7 +12343,6 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, "requires": { "minimist": "^1.2.5" } @@ -11379,6 +12415,16 @@ "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-filter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", @@ -11453,6 +12499,12 @@ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, + "to-gfm-code-block": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz", + "integrity": "sha1-JdBFpfrlUxielje1kJANpzLYqoI=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -11707,6 +12759,26 @@ "is-typedarray": "^1.0.0" } }, + "typeof-article": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/typeof-article/-/typeof-article-0.1.1.tgz", + "integrity": "sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8=", + "dev": true, + "requires": { + "kind-of": "^3.1.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" + } + } + } + }, "typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", @@ -11719,6 +12791,13 @@ "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", "dev": true }, + "uglify-js": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", + "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "dev": true, + "optional": true + }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -12120,6 +13199,12 @@ "loose-envify": "^1.0.0" } }, + "warning-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/warning-symbol/-/warning-symbol-0.1.0.tgz", + "integrity": "sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE=", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12272,6 +13357,12 @@ "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": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", @@ -12281,8 +13372,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "optional": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yargs": { "version": "15.4.1", @@ -12632,6 +13722,12 @@ "fd-slicer": "~1.1.0" } }, + "year": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/year/-/year-0.2.1.tgz", + "integrity": "sha1-QIOuUgoxiyPshgN/MADLiSvfm7A=", + "dev": true + }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/gui/package.json b/gui/package.json index ff29dcbef0..927db17ba9 100644 --- a/gui/package.json +++ b/gui/package.json @@ -13,10 +13,12 @@ "license": "GPL-3.0", "dependencies": { "argv-split": "^2.0.1", + "@grpc/grpc-js": "^1.1.2", "connected-react-router": "^6.8.0", "d3-geo-projection": "^2.7.0", "electron-log": "^4.1.1", "gettext-parser": "^4.0.3", + "google-protobuf": "^4.0.0-rc.2", "history": "^4.6.1", "jsonrpc-lite": "^2.0.7", "linux-app-list": "^1.0.1", @@ -47,6 +49,7 @@ "@types/d3-geo": "^1.11.0", "@types/enzyme": "^3.1.15", "@types/enzyme-adapter-react-16": "^1.0.3", + "@types/google-protobuf": "^3.7.2", "@types/mkdirp": "^1.0.0", "@types/mocha": "^5.2.6", "@types/node": "^10.12.3", @@ -81,6 +84,8 @@ "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.18.3", "gettext-extractor": "^3.5.2", + "grpc-tools": "^1.9.1", + "grpc_tools_node_protoc_ts": "^4.1.0", "gulp": "^4.0.2", "gulp-inject-string": "^1.1.2", "gulp-typescript": "^5.0.1", @@ -94,6 +99,7 @@ "scripts": { "postinstall": "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true electron-builder install-app-deps", "build": "gulp build", + "build-proto": "gulp build-proto", "lint": "eslint --ext tsx,ts .", "format": "prettier \"**/*.{js,css,ts,tsx}\" --write", "develop": "gulp develop", diff --git a/gui/scripts/build-proto.sh b/gui/scripts/build-proto.sh new file mode 100755 index 0000000000..a6cf6a5d94 --- /dev/null +++ b/gui/scripts/build-proto.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -eu + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +NODE_MODULES_DIR="$(cd ../node_modules/.bin && pwd)" +PROTO_DIR="../../mullvad-management-interface/proto" +PROTO_FILENAME="management_interface.proto" +DESTINATION_DIR="../build/src/main/management_interface" +TYPES_DESTINATION_DIR="../src/main/management_interface" + +TS_PROTOC_PLUGIN="$NODE_MODULES_DIR/protoc-gen-ts" +if [[ "$(uname -s)" == "MINGW"* ]]; then + TS_PROTOC_PLUGIN="$TS_PROTOC_PLUGIN.cmd" +fi + +mkdir -p $DESTINATION_DIR +mkdir -p $TYPES_DESTINATION_DIR + +"$NODE_MODULES_DIR/grpc_tools_node_protoc" \ + --js_out=import_style=commonjs,binary:$DESTINATION_DIR \ + --grpc_out=generate_package_definition:$DESTINATION_DIR \ + --proto_path=$PROTO_DIR \ + $PROTO_DIR/$PROTO_FILENAME + +"$NODE_MODULES_DIR/grpc_tools_node_protoc" \ + --plugin=protoc-gen-ts=$TS_PROTOC_PLUGIN \ + --ts_out=generate_package_definition:$TYPES_DESTINATION_DIR \ + --proto_path=$PROTO_DIR \ + $PROTO_DIR/$PROTO_FILENAME + diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index c3d3fe33a6..ff755e2a36 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -1,324 +1,75 @@ +import * as grpc from '@grpc/grpc-js'; +import { + BoolValue, + StringValue, + UInt32Value, +} from 'google-protobuf/google/protobuf/wrappers_pb.js'; +import log from 'electron-log'; +import { Empty } from 'google-protobuf/google/protobuf/empty_pb.js'; +import { promisify } from 'util'; import { AccountToken, + Constraint, + IRelayListCountry, + IRelayListCity, + IRelayListHostname, + IWireguardTunnelData, + IBridgeConstraints, + IWireguardConstraints, + ITunnelOptions, + IOpenVpnConstraints, + IRelayList, + IShadowsocksEndpointData, + RelayProtocol, BridgeSettings, + FirewallPolicyError, BridgeState, - DaemonEvent, - IAccountData, - IAppVersionInfo, ILocation, - IRelayList, - ISettings, - IWireguardPublicKey, + IAppVersionInfo, + IAccountData, + IOpenVpnTunnelData, + TunnelState, + AfterDisconnect, + IErrorState, + ErrorStateCause, + TunnelParameterError, + ITunnelStateRelayInfo, + TunnelType, + IProxyEndpoint, + ProxyType, KeygenEvent, + IWireguardPublicKey, + ISettings, + ConnectionConfig, + DaemonEvent, + RelaySettings, RelaySettingsUpdate, - TunnelState, - VoucherErrorCode, + RelayLocation, + ProxySettings, VoucherResponse, + TunnelProtocol, } from '../shared/daemon-rpc-types'; -import { CommunicationError, InvalidAccountError, NoDaemonError } from './errors'; -import JsonRpcClient, { - RemoteError as JsonRpcRemoteError, - SocketTransport, - TimeOutError as JsonRpcTimeOutError, -} from './jsonrpc-client'; -import { camelCaseToSnakeCase, snakeCaseToCamelCase } from './transform-object-keys'; - -import { validate } from 'validated/object'; -import { - arrayOf, - boolean, - enumeration, - maybe, - Node as SchemaNode, - number, - object, - oneOf, - partialObject, - string, -} from 'validated/schema'; - -const locationSchema = maybe( - partialObject({ - ipv4: maybe(string), - ipv6: maybe(string), - country: string, - city: maybe(string), - latitude: number, - longitude: number, - mullvad_exit_ip: boolean, - hostname: maybe(string), - bridge_hostname: maybe(string), - }), -); - -const constraint = <T>(constraintValue: SchemaNode<T>) => { - return oneOf( - string, // any - object({ - only: constraintValue, - }), - ); -}; -const locationConstraintSchema = constraint( - oneOf( - object({ - hostname: arrayOf(string), - }), - object({ - city: arrayOf(string), - }), - object({ - country: string, - }), - ), -); - -const customTunnelEndpointSchema = oneOf( - object({ - openvpn: object({ - endpoint: object({ - address: string, - protocol: enumeration('udp', 'tcp'), - }), - username: string, - password: string, - }), - }), - object({ - wireguard: object({ - tunnel: object({ - private_key: string, - addresses: arrayOf(string), - }), - peer: object({ - public_key: string, - allowed_ips: arrayOf(string), - endpoint: string, - }), - ipv4_gateway: string, - ipv6_gateway: maybe(string), - }), - }), -); - -const relaySettingsSchema = oneOf( - object({ - normal: partialObject({ - location: locationConstraintSchema, - tunnel_protocol: constraint(enumeration('wireguard', 'openvpn')), - wireguard_constraints: partialObject({ - port: constraint(number), - }), - openvpn_constraints: partialObject({ - port: constraint(number), - protocol: constraint(enumeration('udp', 'tcp')), - }), - }), - }), - object({ - custom_tunnel_endpoint: partialObject({ - host: string, - config: customTunnelEndpointSchema, - }), - }), -); +import * as managementInterface from './management_interface/management_interface_grpc_pb'; +import * as grpcTypes from './management_interface/management_interface_pb'; +import { CommunicationError, InvalidAccountError } from './errors'; +import consumePromise from '../shared/promise'; -const relayListSchema = partialObject({ - countries: arrayOf( - partialObject({ - name: string, - code: string, - cities: arrayOf( - partialObject({ - name: string, - code: string, - latitude: number, - longitude: number, - relays: arrayOf( - partialObject({ - hostname: string, - ipv4_addr_in: string, - include_in_country: boolean, - active: boolean, - weight: number, - bridges: maybe( - partialObject({ - shadowsocks: arrayOf( - object({ - port: number, - cipher: string, - password: string, - protocol: enumeration('tcp', 'udp'), - }), - ), - }), - ), - tunnels: maybe( - partialObject({ - openvpn: arrayOf( - partialObject({ - port: number, - protocol: string, - }), - ), - wireguard: arrayOf( - partialObject({ - port_ranges: arrayOf(arrayOf(number)), - public_key: string, - }), - ), - }), - ), - }), - ), - }), - ), - }), - ), -}); - -const openVpnProxySchema = maybe( - oneOf( - object({ - local: partialObject({ - port: number, - peer: string, - }), - }), - object({ - remote: partialObject({ - address: string, - auth: maybe( - partialObject({ - username: string, - password: string, - }), - ), - }), - }), - object({ - shadowsocks: partialObject({ - peer: string, - password: string, - cipher: string, - }), - }), - ), -); +const NETWORK_CALL_TIMEOUT = 10000; +const CHANNEL_STATE_TIMEOUT = 1000 * 60 * 60; -const bridgeSettingsSchema = oneOf( - partialObject({ normal: partialObject({ location: locationConstraintSchema }) }), - partialObject({ custom: openVpnProxySchema }), +const ManagementServiceClient = grpc.makeClientConstructor( + // @ts-ignore + managementInterface['mullvad_daemon.management_interface.ManagementService'], + 'ManagementService', ); -const tunnelOptionsSchema = partialObject({ - openvpn: partialObject({ - mssfix: maybe(number), - }), - wireguard: partialObject({ - mtu: maybe(number), - // only relevant on linux - fmwark: maybe(number), - }), - generic: partialObject({ - enable_ipv6: boolean, - }), -}); - -const accountDataSchema = partialObject({ - expiry: string, -}); - -const voucherResponseSchema = partialObject({ - new_expiry: string, -}); - -const tunnelStateSchema = oneOf( - object({ - state: enumeration('disconnecting'), - details: enumeration('nothing', 'block', 'reconnect'), - }), - object({ - state: enumeration('connecting', 'connected'), - details: object({ - endpoint: partialObject({ - address: string, - protocol: enumeration('tcp', 'udp'), - tunnel_type: enumeration('wireguard', 'openvpn'), - proxy: maybe( - partialObject({ - address: string, - protocol: enumeration('tcp', 'udp'), - proxy_type: enumeration('shadowsocks', 'custom'), - }), - ), - }), - location: maybe(locationSchema), - }), - }), - object({ - state: enumeration('error'), - details: object({ - block_failure: maybe( - object({ - reason: enumeration('generic', 'locked'), - details: maybe( - object({ - name: string, - pid: number, - }), - ), - }), - ), - cause: oneOf( - object({ - reason: enumeration( - 'ipv6_unavailable', - 'set_dns_error', - 'start_tunnel_error', - 'is_offline', - 'tap_adapter_problem', - ), - }), - object({ - reason: enumeration('set_firewall_policy_error'), - details: object({ - reason: enumeration('generic', 'locked'), - details: maybe( - object({ - name: string, - pid: number, - }), - ), - }), - }), - object({ - reason: enumeration('auth_failed'), - details: maybe(string), - }), - object({ - reason: enumeration('tunnel_parameter_error'), - details: enumeration( - 'no_matching_relay', - 'no_matching_bridge_relay', - 'no_wireguard_key', - 'custom_tunnel_host_resultion_error', - ), - }), - ), - }), - }), - object({ - state: enumeration('connected', 'connecting', 'disconnected'), - }), +const noConnectionError = new Error('No connection established to daemon'); +const configNotSupported = new Error('Setting custom settings is not supported'); +const invalidErrorStateCause = new Error( + 'VPN_PERMISSION_DENIED is not a valid error state cause on desktop', ); -const appVersionInfoSchema = partialObject({ - supported: boolean, - suggested_upgrade: maybe(string), -}); - export class ConnectionObserver { constructor(private openHandler: () => void, private closeHandler: (error?: Error) => void) {} @@ -338,7 +89,7 @@ export class ConnectionObserver { export class SubscriptionListener<T> { // Only meant to be used by DaemonRpc // @internal - public subscriptionId?: string | number; + public subscriptionId?: number; constructor( private eventHandler: (payload: T) => void, @@ -358,328 +109,1130 @@ export class SubscriptionListener<T> { } } -const settingsSchema = partialObject({ - account_token: maybe(string), - allow_lan: boolean, - auto_connect: boolean, - block_when_disconnected: boolean, - show_beta_releases: boolean, - bridge_settings: bridgeSettingsSchema, - bridge_state: enumeration('on', 'auto', 'off'), - relay_settings: relaySettingsSchema, - tunnel_options: tunnelOptionsSchema, -}); +export class ResponseParseError extends Error { + constructor(message: string) { + super(message); + } +} -const wireguardPublicKey = object({ - key: string, - created: string, -}); +type CallFunctionArgument<T, R> = + | ((arg: T, callback: (error: Error | null, result: R) => void) => void) + | undefined; -const keygenEventSchema = oneOf( - enumeration('too_many_keys', 'generation_failure'), - object({ - new_key: object({ - key: string, - created: string, - }), - }), -); +export class DaemonRpc { + constructor(connectionParams: string) { + this.client = (new ManagementServiceClient( + connectionParams, + grpc.credentials.createInsecure(), + this.channelOptions(), + ) as unknown) as managementInterface.ManagementServiceClient; + } -const daemonEventSchema = oneOf( - object({ - tunnel_state: tunnelStateSchema, - }), - object({ - settings: settingsSchema, - }), - object({ - relay_list: relayListSchema, - }), - object({ - wireguard_key: keygenEventSchema, - }), - object({ - app_version_info: appVersionInfoSchema, - }), -); + private client: managementInterface.ManagementServiceClient; + private isConnected = false; + private connectionObservers: ConnectionObserver[] = []; + private nextSubscriptionId = 0; + private subscriptions: Map<number, grpc.ClientReadableStream<grpcTypes.DaemonEvent>> = new Map(); + private reconnectionTimeout?: number; -export class ResponseParseError extends Error { - constructor(message: string, private validationErrorValue?: Error) { - super(message); + private subscriptionId(): number { + const current = this.nextSubscriptionId; + this.nextSubscriptionId += 1; + return current; } - get validationError(): Error | undefined { - return this.validationErrorValue; + private deadlineFromNow() { + return Date.now() + NETWORK_CALL_TIMEOUT; } -} -// Timeout used for RPC calls that do networking -const NETWORK_CALL_TIMEOUT = 10000; + private channelStateTimeout(): number { + return Date.now() + CHANNEL_STATE_TIMEOUT; + } -export class DaemonRpc { - private transport = new JsonRpcClient(new SocketTransport()); + private callEmpty<R>(fn: CallFunctionArgument<Empty, R>): Promise<R> { + return this.call<Empty, R>(fn, new Empty()); + } + + private callString<R>(fn: CallFunctionArgument<StringValue, R>, value?: string): Promise<R> { + const googleString = new StringValue(); + + if (value !== undefined) { + googleString.setValue(value); + } + + return this.call<StringValue, R>(fn, googleString); + } + + private callBool<R>(fn: CallFunctionArgument<BoolValue, R>, value?: boolean): Promise<R> { + const googleBool = new BoolValue(); + + if (value !== undefined) { + googleBool.setValue(value); + } + + return this.call<BoolValue, R>(fn, googleBool); + } + + private callNumber<R>(fn: CallFunctionArgument<UInt32Value, R>, value?: number): Promise<R> { + const googleNumber = new UInt32Value(); + + if (value !== undefined) { + googleNumber.setValue(value); + } + + return this.call<UInt32Value, R>(fn, googleNumber); + } + + private call<T, R>(fn: CallFunctionArgument<T, R>, arg: T): Promise<R> { + if (fn && this.isConnected) { + return promisify<T, R>(fn.bind(this.client))(arg); + } else { + throw noConnectionError; + } + } + + public connect(): Promise<void> { + return new Promise((resolve, reject) => { + this.client.waitForReady(this.deadlineFromNow(), (error) => { + if (error) { + this.connectionObservers.forEach((observer) => observer.onClose(error)); + this.ensureConnectivity(); + reject(error); + } else { + this.reconnectionTimeout = undefined; + this.isConnected = true; + this.connectionObservers.forEach((observer) => observer.onOpen()); + this.setChannelCallback(); + resolve(); + } + }); + }); + } - public connect(connectionParams: { path: string }): Promise<void> { - return this.transport.connect(connectionParams); + private channelOptions(): grpc.ClientOptions { + return { + 'grpc.max_reconnect_backoff_ms': 3000, + 'grpc.initial_reconnect_backoff_ms': 3000, + 'grpc.keepalive_time_ms': Math.pow(2, 30), + 'grpc.keepalive_timeout_ms': Math.pow(2, 30), + }; + } + + private connectivityChangeCallback(timeoutErr?: Error) { + const channel = this.client.getChannel(); + const currentState = channel?.getConnectivityState(true); + log.debug(`GRPC Channel connectivity state changed to ${currentState}`); + if (channel) { + if (timeoutErr) { + this.setChannelCallback(currentState); + return; + } + const wasConnected = this.isConnected; + if (this.channelDisconnected(currentState)) { + this.connectionObservers.forEach((observer) => observer.onClose()); + this.isConnected = false; + // Try and reconnect in case + consumePromise( + this.connect().catch((error) => { + log.error(`Failed to reconnect - ${error}`); + }), + ); + this.setChannelCallback(currentState); + } else if (!wasConnected && currentState === grpc.connectivityState.READY) { + this.isConnected = true; + this.connectionObservers.forEach((observer) => observer.onOpen()); + this.setChannelCallback(currentState); + } + } + } + + private channelDisconnected(state: grpc.connectivityState): boolean { + return ( + (state === grpc.connectivityState.SHUTDOWN || + state === grpc.connectivityState.TRANSIENT_FAILURE || + state === grpc.connectivityState.IDLE) && + this.isConnected + ); + } + + private setChannelCallback(currentState?: grpc.connectivityState) { + const channel = this.client.getChannel(); + if (currentState === undefined && channel) { + currentState = channel?.getConnectivityState(false); + } + if (currentState) { + channel.watchConnectivityState(currentState, this.channelStateTimeout(), (error) => + this.connectivityChangeCallback(error), + ); + } + } + + // Since grpc.Channel.watchConnectivityState() isn't always running as intended, whenever the + // client fails to connect at first, `ensureConnectivity()` should be called so that it tries to + // check the connectivity state and nudge the client into connecting. + // `grpc.Channel.getConnectivityState(true)` should make it attempt to connect. + private ensureConnectivity() { + this.reconnectionTimeout = setTimeout(() => { + const lastState = this.client.getChannel().getConnectivityState(true); + if (this.channelDisconnected(lastState)) { + this.connectionObservers.forEach((observer) => observer.onClose()); + this.isConnected = false; + } + if (!this.isConnected) { + consumePromise( + this.connect().catch((error) => { + log.error(`Failed to reconnect - ${error}`); + }), + ); + } + }, 3000); } public disconnect() { - this.transport.disconnect(); + this.isConnected = false; + this.subscriptions.clear(); + this.client.close(); + if (this.reconnectionTimeout) { + clearTimeout(this.reconnectionTimeout); + } } public addConnectionObserver(observer: ConnectionObserver) { - this.transport.on('open', observer.onOpen).on('close', observer.onClose); + this.connectionObservers.push(observer); + const currentState = this.client.getChannel()?.getConnectivityState(true); + if ( + currentState === grpc.connectivityState.SHUTDOWN || + currentState === grpc.connectivityState.TRANSIENT_FAILURE || + currentState === grpc.connectivityState.IDLE + ) { + observer.onClose(); + } else { + observer.onOpen(); + } } public removeConnectionObserver(observer: ConnectionObserver) { - this.transport.off('open', observer.onOpen).off('close', observer.onClose); + const index = this.connectionObservers.indexOf(observer); + if (index !== -1) { + this.connectionObservers.splice(index, 1); + } } public async getAccountData(accountToken: AccountToken): Promise<IAccountData> { - let response; try { - response = await this.transport.send('get_account_data', accountToken, NETWORK_CALL_TIMEOUT); + const response = await this.callString<grpcTypes.AccountData>( + this.client.getAccountData, + accountToken, + ); + const expiry = response.getExpiry()!.toDate().toISOString(); + return { expiry }; } catch (error) { - if (error instanceof JsonRpcRemoteError) { + if (error.code) { switch (error.code) { - case -200: // Account doesn't exist + case grpc.status.UNAUTHENTICATED: throw new InvalidAccountError(); - case -32603: // Internal error + default: throw new CommunicationError(); } - } else if (error instanceof JsonRpcTimeOutError) { - throw new NoDaemonError(); - } else { - throw error; } - } - - try { - return validate(accountDataSchema, response); - } catch (error) { - throw new ResponseParseError('Invalid response from get_account_data', error); + throw error; } } public async getWwwAuthToken(): Promise<string> { - const response = await this.transport.send('get_www_auth_token'); - try { - return validate(string, response); - } catch (error) { - throw new ResponseParseError('Invalid response from get_www_auth_token', error); - } + const response = await this.callEmpty<StringValue>(this.client.getWwwAuthToken); + return response.getValue(); } public async submitVoucher(voucherCode: string): Promise<VoucherResponse> { try { - const response = await this.transport.send('submit_voucher', voucherCode); - const new_expiry = validate(voucherResponseSchema, response).new_expiry; - return { type: 'success', new_expiry }; + const response = await this.callString<grpcTypes.VoucherSubmission>( + this.client.submitVoucher, + voucherCode, + ); + + const secondsAdded = ensureExists( + response.getSecondsAdded(), + "no 'secondsAdded' field in voucher response", + ); + const newExpiry = ensureExists( + response.getNewExpiry(), + "no 'newExpiry' field in voucher response", + ) + .toDate() + .toISOString(); + return { + type: 'success', + secondsAdded, + newExpiry, + }; } catch (error) { - if (error instanceof JsonRpcRemoteError) { + if (error.code) { switch (error.code) { - case VoucherErrorCode.Invalid: + case grpc.status.NOT_FOUND: return { type: 'invalid' }; - case VoucherErrorCode.AlreadyUsed: + case grpc.status.RESOURCE_EXHAUSTED: return { type: 'already_used' }; } } + return { type: 'error' }; } - - return { type: 'error' }; } - public async getRelayLocations(): Promise<IRelayList> { - const response = await this.transport.send('get_relay_locations'); - try { - return snakeCaseToCamelCase(validate(relayListSchema, response)); - } catch (error) { - throw new ResponseParseError(`Invalid response from get_relay_locations: ${error}`, error); + public getRelayLocations(): Promise<IRelayList> { + if (this.isConnected) { + return new Promise((resolve, reject) => { + const relayLocations: IRelayListCountry[] = []; + const stream = this.client.getRelayLocations(new Empty()); + stream.on('data', (country: grpcTypes.RelayListCountry) => + relayLocations.push(convertFromRelayListCountry(country.toObject())), + ); + stream.on('end', () => resolve({ countries: relayLocations })); + stream.on('close', reject); + }); + } else { + throw noConnectionError; } } public async createNewAccount(): Promise<string> { - const response = await this.transport.send('create_new_account'); - return validate(string, response); + const response = await this.callEmpty<StringValue>(this.client.createNewAccount); + return response.getValue(); } public async setAccount(accountToken?: AccountToken): Promise<void> { - await this.transport.send('set_account', [accountToken]); + await this.callString(this.client.setAccount, accountToken); } + // TODO: Custom tunnel configurations are not supported by the GUI. public async updateRelaySettings(relaySettings: RelaySettingsUpdate): Promise<void> { - await this.transport.send('update_relay_settings', [camelCaseToSnakeCase(relaySettings)]); + if ('normal' in relaySettings) { + const settingsUpdate = relaySettings.normal; + const grpcRelaySettings = new grpcTypes.RelaySettingsUpdate(); + + const normalUpdate = new grpcTypes.NormalRelaySettingsUpdate(); + + if (settingsUpdate.tunnelProtocol) { + const tunnelTypeUpdate = new grpcTypes.TunnelTypeUpdate(); + tunnelTypeUpdate.setTunnelType( + convertToTunnelTypeConstraint(settingsUpdate.tunnelProtocol), + ); + normalUpdate.setTunnelType(tunnelTypeUpdate); + } + + if (settingsUpdate.location) { + normalUpdate.setLocation(convertToLocation(liftConstraint(settingsUpdate.location))); + } + + if (settingsUpdate.wireguardConstraints) { + normalUpdate.setWireguardConstraints( + convertToWireguardConstraints(settingsUpdate.wireguardConstraints), + ); + } + + if (settingsUpdate.openvpnConstraints) { + normalUpdate.setOpenvpnConstraints( + convertToOpenVpnConstraints(settingsUpdate.openvpnConstraints), + ); + } + + grpcRelaySettings.setNormal(normalUpdate); + await this.call<grpcTypes.RelaySettingsUpdate, Empty>( + this.client.updateRelaySettings, + grpcRelaySettings, + ); + } } public async setAllowLan(allowLan: boolean): Promise<void> { - await this.transport.send('set_allow_lan', [allowLan]); + await this.callBool(this.client.setAllowLan, allowLan); } public async setShowBetaReleases(showBetaReleases: boolean): Promise<void> { - await this.transport.send('set_show_beta_releases', [showBetaReleases]); + await this.callBool(this.client.setShowBetaReleases, showBetaReleases); } public async setEnableIpv6(enableIpv6: boolean): Promise<void> { - await this.transport.send('set_enable_ipv6', [enableIpv6]); + await this.callBool(this.client.setEnableIpv6, enableIpv6); } public async setBlockWhenDisconnected(blockWhenDisconnected: boolean): Promise<void> { - await this.transport.send('set_block_when_disconnected', [blockWhenDisconnected]); + await this.callBool(this.client.setBlockWhenDisconnected, blockWhenDisconnected); } public async setBridgeState(bridgeState: BridgeState): Promise<void> { - await this.transport.send('set_bridge_state', [bridgeState]); + const bridgeStateMap = { + auto: grpcTypes.BridgeState.State.AUTO, + on: grpcTypes.BridgeState.State.ON, + off: grpcTypes.BridgeState.State.OFF, + }; + + const grpcBridgeState = new grpcTypes.BridgeState(); + grpcBridgeState.setState(bridgeStateMap[bridgeState]); + await this.call<grpcTypes.BridgeState, Empty>(this.client.setBridgeState, grpcBridgeState); } public async setBridgeSettings(bridgeSettings: BridgeSettings): Promise<void> { - await this.transport.send('set_bridge_settings', [bridgeSettings]); + const grpcBridgeSettings = new grpcTypes.BridgeSettings(); + + if ('normal' in bridgeSettings) { + const normalSettings = convertToNormalBridgeSettings(bridgeSettings.normal); + grpcBridgeSettings.setNormal(normalSettings); + } + + if ('custom' in bridgeSettings) { + throw configNotSupported; + } + + await this.call<grpcTypes.BridgeSettings, Empty>( + this.client.setBridgeSettings, + grpcBridgeSettings, + ); } public async setOpenVpnMssfix(mssfix?: number): Promise<void> { - await this.transport.send('set_openvpn_mssfix', [mssfix]); + await this.callNumber(this.client.setOpenvpnMssfix, mssfix); } public async setWireguardMtu(mtu?: number): Promise<void> { - await this.transport.send('set_wireguard_mtu', [mtu]); + await this.callNumber(this.client.setWireguardMtu, mtu); } public async setAutoConnect(autoConnect: boolean): Promise<void> { - await this.transport.send('set_auto_connect', [autoConnect]); + await this.callBool(this.client.setAutoConnect, autoConnect); } public async connectTunnel(): Promise<void> { - await this.transport.send('connect'); + await this.callEmpty(this.client.connectTunnel); } public async disconnectTunnel(): Promise<void> { - await this.transport.send('disconnect'); + await this.callEmpty(this.client.disconnectTunnel); } public async reconnectTunnel(): Promise<void> { - await this.transport.send('reconnect'); + await this.callEmpty(this.client.reconnectTunnel); } - public async getLocation(): Promise<ILocation | undefined> { - const response = await this.transport.send('get_current_location', [], NETWORK_CALL_TIMEOUT); - try { - const validatedObject = validate(locationSchema, response); - if (validatedObject) { - return snakeCaseToCamelCase(validatedObject); - } else { - return undefined; - } - } catch (error) { - throw new ResponseParseError('Invalid response from get_current_location', error); - } + public async getLocation(): Promise<ILocation> { + const response = await this.callEmpty<grpcTypes.GeoIpLocation>(this.client.getCurrentLocation); + return response.toObject(); } public async getState(): Promise<TunnelState> { - const response = await this.transport.send('get_state'); - try { - return snakeCaseToCamelCase(validate(tunnelStateSchema, response)); - } catch (error) { - throw new ResponseParseError('Invalid response from get_state', error); - } + const response = await this.callEmpty<grpcTypes.TunnelState>(this.client.getTunnelState); + return convertFromTunnelState(response)!; } public async getSettings(): Promise<ISettings> { - const response = await this.transport.send('get_settings'); - try { - return snakeCaseToCamelCase(validate(settingsSchema, response)); - } catch (error) { - throw new ResponseParseError('Invalid response from get_settings', error); - } + const response = await this.callEmpty<grpcTypes.Settings>(this.client.getSettings); + return convertFromSettings(response)!; } - public async subscribeDaemonEventListener( - listener: SubscriptionListener<DaemonEvent>, - ): Promise<void> { - const subscriptionId = await this.transport.subscribe('daemon_event', (payload) => { - let daemonEvent: DaemonEvent; + public subscribeDaemonEventListener(listener: SubscriptionListener<DaemonEvent>) { + const call = this.isConnected && this.client.eventsListen(new Empty()); + if (!call) { + throw noConnectionError; + } + const subscriptionId = this.subscriptionId(); + listener.subscriptionId = subscriptionId; + this.subscriptions.set(subscriptionId, call); + call.on('data', (data: grpcTypes.DaemonEvent) => { try { - daemonEvent = snakeCaseToCamelCase(validate(daemonEventSchema, payload)); - } catch (error) { - listener.onError(new ResponseParseError('Invalid payload from daemon_event', error)); - return; + const daemonEvent = convertFromDaemonEvent(data); + listener.onEvent(daemonEvent); + } catch (err) { + listener.onError(err); } + }); - listener.onEvent(daemonEvent); + call.on('error', (error) => { + // if the subscription was cancelled by the client, there's no reason to + // invoke the onError handler + if ( + 'code' in error && + error['code'] === grpc.status.CANCELLED && + !this.subscriptions.has(subscriptionId) + ) { + return; + } + listener.onError(error); + this.removeSubscription(subscriptionId); }); + } - listener.subscriptionId = subscriptionId; + public unsubscribeDaemonEventListener(listener: SubscriptionListener<DaemonEvent>) { + const id = listener.subscriptionId; + if (id !== undefined) { + this.removeSubscription(id); + } } - public async unsubscribeDaemonEventListener( - listener: SubscriptionListener<DaemonEvent>, - ): Promise<void> { - if (listener.subscriptionId) { - return this.transport.unsubscribe('daemon_event', listener.subscriptionId); + private removeSubscription(id: number) { + const subscription = this.subscriptions.get(id); + if (subscription !== undefined) { + this.subscriptions.delete(id); + subscription.cancel(); } } public async getAccountHistory(): Promise<AccountToken[]> { - const response = await this.transport.send('get_account_history'); - try { - return validate(arrayOf(string), response); - } catch (error) { - throw new ResponseParseError('Invalid response from get_account_history'); - } + const response = await this.callEmpty<grpcTypes.AccountHistory>(this.client.getAccountHistory); + return response.toObject().tokenList; } public async removeAccountFromHistory(accountToken: AccountToken): Promise<void> { - await this.transport.send('remove_account_from_history', accountToken); + await this.callString(this.client.removeAccountFromHistory, accountToken); } public async getCurrentVersion(): Promise<string> { - const response = await this.transport.send('get_current_version'); - try { - return validate(string, response); - } catch (error) { - throw new ResponseParseError('Invalid response from get_current_version'); - } + const response = await this.callEmpty<StringValue>(this.client.getCurrentVersion); + return response.getValue(); } public async generateWireguardKey(): Promise<KeygenEvent> { - const response = await this.transport.send('generate_wireguard_key'); - try { - const validatedResponse = validate(keygenEventSchema, response); - switch (validatedResponse) { - case 'too_many_keys': - case 'generation_failure': - return validatedResponse; - default: - return snakeCaseToCamelCase(validatedResponse as object); + const response = await this.callEmpty<grpcTypes.KeygenEvent>(this.client.generateWireguardKey); + return convertFromKeygenEvent(response); + } + + public async getWireguardKey(): Promise<IWireguardPublicKey> { + const response = await this.callEmpty<grpcTypes.PublicKey>(this.client.getWireguardKey); + return { + created: response.getCreated()!.toDate().toISOString(), + key: convertFromWireguardKey(response.getKey()), + }; + } + + public async verifyWireguardKey(): Promise<boolean> { + const response = await this.callEmpty<BoolValue>(this.client.verifyWireguardKey); + return response.getValue(); + } + + public async getVersionInfo(): Promise<IAppVersionInfo> { + const response = await this.callEmpty<grpcTypes.AppVersionInfo>(this.client.getVersionInfo); + return response.toObject(); + } +} + +function liftConstraint<T>(constraint: Constraint<T> | undefined): T | undefined { + if (constraint !== undefined && constraint !== 'any') { + return constraint.only; + } + return undefined; +} + +function convertFromRelayListCountry( + country: grpcTypes.RelayListCountry.AsObject, +): IRelayListCountry { + return { + ...country, + cities: country.citiesList.map(convertFromRelayListCity), + }; +} + +function convertFromRelayListCity(city: grpcTypes.RelayListCity.AsObject): IRelayListCity { + return { + ...city, + relays: city.relaysList.map(convertFromRelayListRelay), + }; +} + +function convertFromRelayListRelay(relay: grpcTypes.Relay.AsObject): IRelayListHostname { + return { + ...relay, + tunnels: relay.tunnels && { + ...relay.tunnels, + openvpn: relay.tunnels.openvpnList.map(convertFromOpenvpnList), + wireguard: relay.tunnels.wireguardList.map(convertFromWireguardList), + }, + bridges: relay.bridges && { + shadowsocks: relay.bridges.shadowsocksList.map(convertFromShadowsocksList), + }, + }; +} + +function convertFromOpenvpnList( + openvpn: grpcTypes.OpenVpnEndpointData.AsObject, +): IOpenVpnTunnelData { + return { + ...openvpn, + protocol: convertFromTransportProtocol(openvpn.protocol), + }; +} + +function convertFromWireguardList( + wireguard: grpcTypes.WireguardEndpointData.AsObject, +): IWireguardTunnelData { + return { + ...wireguard, + portRanges: wireguard.portRangesList, + publicKey: convertFromWireguardKey(wireguard.publicKey), + }; +} + +function convertFromWireguardKey(publicKey: Uint8Array | string): string { + if (typeof publicKey === 'string') { + return publicKey; + } + return Buffer.from(publicKey).toString('base64'); +} + +function convertFromShadowsocksList( + shadowsocks: grpcTypes.ShadowsocksEndpointData.AsObject, +): IShadowsocksEndpointData { + return { + ...shadowsocks, + protocol: convertFromTransportProtocol(shadowsocks.protocol), + }; +} + +function convertFromTransportProtocol(protocol: grpcTypes.TransportProtocol): RelayProtocol { + const protocolMap: Record<grpcTypes.TransportProtocol, RelayProtocol> = { + [grpcTypes.TransportProtocol.TCP]: 'tcp', + [grpcTypes.TransportProtocol.UDP]: 'udp', + }; + return protocolMap[protocol]; +} + +function convertFromTunnelState(tunnelState: grpcTypes.TunnelState): TunnelState | undefined { + const tunnelStateObject = tunnelState.toObject(); + switch (tunnelState.getStateCase()) { + case grpcTypes.TunnelState.StateCase.STATE_NOT_SET: + return undefined; + case grpcTypes.TunnelState.StateCase.DISCONNECTED: + return { state: 'disconnected' }; + case grpcTypes.TunnelState.StateCase.DISCONNECTING: { + const detailsMap: Record<grpcTypes.AfterDisconnect, AfterDisconnect> = { + [grpcTypes.AfterDisconnect.NOTHING]: 'nothing', + [grpcTypes.AfterDisconnect.BLOCK]: 'block', + [grpcTypes.AfterDisconnect.RECONNECT]: 'reconnect', + }; + return ( + tunnelStateObject.disconnecting && { + state: 'disconnecting', + details: detailsMap[tunnelStateObject.disconnecting.afterDisconnect], + } + ); + } + case grpcTypes.TunnelState.StateCase.ERROR: + return ( + tunnelStateObject.error?.errorState && { + state: 'error', + details: convertFromTunnelStateError(tunnelStateObject.error.errorState), + } + ); + case grpcTypes.TunnelState.StateCase.CONNECTING: + return { + state: 'connecting', + details: + tunnelStateObject.connecting?.relayInfo && + convertFromTunnelStateRelayInfo(tunnelStateObject.connecting.relayInfo), + }; + case grpcTypes.TunnelState.StateCase.CONNECTED: { + const relayInfo = + tunnelStateObject.connected?.relayInfo && + convertFromTunnelStateRelayInfo(tunnelStateObject.connected.relayInfo); + return ( + relayInfo && { + state: 'connected', + details: relayInfo, + } + ); + } + } +} + +function convertFromTunnelStateError(state: grpcTypes.ErrorState.AsObject): IErrorState { + return { + ...state, + cause: convertFromTunnelStateErrorCause(state.cause, state), + blockFailure: state.blockingError + ? convertFromFirewallPolicyError(state.blockingError) + : undefined, + }; +} + +function convertFromTunnelStateErrorCause( + cause: grpcTypes.ErrorState.Cause, + state: grpcTypes.ErrorState.AsObject, +): ErrorStateCause { + switch (cause) { + case grpcTypes.ErrorState.Cause.IS_OFFLINE: + return { reason: 'is_offline' }; + case grpcTypes.ErrorState.Cause.SET_DNS_ERROR: + return { reason: 'set_dns_error' }; + case grpcTypes.ErrorState.Cause.IPV6_UNAVAILABLE: + return { reason: 'ipv6_unavailable' }; + case grpcTypes.ErrorState.Cause.START_TUNNEL_ERROR: + return { reason: 'start_tunnel_error' }; + case grpcTypes.ErrorState.Cause.TAP_ADAPTER_PROBLEM: + return { reason: 'tap_adapter_problem' }; + case grpcTypes.ErrorState.Cause.SET_FIREWALL_POLICY_ERROR: + return { + reason: 'set_firewall_policy_error', + details: convertFromFirewallPolicyError(state.policyError!), + }; + case grpcTypes.ErrorState.Cause.AUTH_FAILED: + return { reason: 'auth_failed', details: state.authFailReason }; + case grpcTypes.ErrorState.Cause.TUNNEL_PARAMETER_ERROR: { + const parameterErrorMap: Record< + grpcTypes.ErrorState.GenerationError, + TunnelParameterError + > = { + [grpcTypes.ErrorState.GenerationError.NO_MATCHING_RELAY]: 'no_matching_relay', + [grpcTypes.ErrorState.GenerationError.NO_MATCHING_BRIDGE_RELAY]: 'no_matching_bridge_relay', + [grpcTypes.ErrorState.GenerationError.NO_WIREGUARD_KEY]: 'no_wireguard_key', + [grpcTypes.ErrorState.GenerationError.CUSTOM_TUNNEL_HOST_RESOLUTION_ERROR]: + 'custom_tunnel_host_resultion_error', + }; + return { reason: 'tunnel_parameter_error', details: parameterErrorMap[state.parameterError] }; + } + case grpcTypes.ErrorState.Cause.VPN_PERMISSION_DENIED: + // VPN_PERMISSION_DENIED is only ever created on Android + throw invalidErrorStateCause; + } +} + +function convertFromFirewallPolicyError( + error: grpcTypes.ErrorState.FirewallPolicyError.AsObject, +): FirewallPolicyError { + switch (error.type) { + case grpcTypes.ErrorState.FirewallPolicyError.ErrorType.GENERIC: + return { reason: 'generic' }; + case grpcTypes.ErrorState.FirewallPolicyError.ErrorType.LOCKED: { + const pid = error.lockPid; + const name = error.lockName; + return { reason: 'locked', details: pid && name ? { pid, name } : undefined }; + } + } +} + +function convertFromTunnelStateRelayInfo( + state: grpcTypes.TunnelStateRelayInfo.AsObject, +): ITunnelStateRelayInfo | undefined { + if (state.tunnelEndpoint) { + return { + ...state, + endpoint: { + ...state.tunnelEndpoint, + tunnelType: convertFromTunnelType(state.tunnelEndpoint.tunnelType), + protocol: convertFromTransportProtocol(state.tunnelEndpoint.protocol), + proxy: state.tunnelEndpoint.proxy && convertFromProxyEndpoint(state.tunnelEndpoint.proxy), + }, + }; + } + return undefined; +} + +function convertFromTunnelType(tunnelType: grpcTypes.TunnelType): TunnelType { + const tunnelTypeMap: Record<grpcTypes.TunnelType, TunnelType> = { + [grpcTypes.TunnelType.WIREGUARD]: 'wireguard', + [grpcTypes.TunnelType.OPENVPN]: 'openvpn', + }; + + return tunnelTypeMap[tunnelType]; +} + +function convertFromProxyEndpoint(proxyEndpoint: grpcTypes.ProxyEndpoint.AsObject): IProxyEndpoint { + const proxyTypeMap: Record<grpcTypes.ProxyType, ProxyType> = { + [grpcTypes.ProxyType.CUSTOM]: 'custom', + [grpcTypes.ProxyType.SHADOWSOCKS]: 'shadowsocks', + }; + + return { + ...proxyEndpoint, + protocol: convertFromTransportProtocol(proxyEndpoint.protocol), + proxyType: proxyTypeMap[proxyEndpoint.proxyType], + }; +} + +function convertFromSettings(settings: grpcTypes.Settings): ISettings | undefined { + const settingsObject = settings.toObject(); + const bridgeState = convertFromBridgeState(settingsObject.bridgeState!.state!); + const relaySettings = convertFromRelaySettings(settings.getRelaySettings())!; + const bridgeSettings = convertFromBridgeSettings(settingsObject.bridgeSettings!); + const tunnelOptions = convertFromTunnelOptions(settingsObject.tunnelOptions!); + return { + ...settings.toObject(), + bridgeState, + relaySettings, + bridgeSettings, + tunnelOptions, + }; +} + +function convertFromBridgeState(bridgeState: grpcTypes.BridgeState.State): BridgeState { + const bridgeStateMap: Record<grpcTypes.BridgeState.State, BridgeState> = { + [grpcTypes.BridgeState.State.AUTO]: 'auto', + [grpcTypes.BridgeState.State.ON]: 'on', + [grpcTypes.BridgeState.State.OFF]: 'off', + }; + + return bridgeStateMap[bridgeState]; +} + +function convertFromRelaySettings( + relaySettings?: grpcTypes.RelaySettings, +): RelaySettings | undefined { + if (relaySettings) { + switch (relaySettings.getEndpointCase()) { + case grpcTypes.RelaySettings.EndpointCase.ENDPOINT_NOT_SET: + return undefined; + case grpcTypes.RelaySettings.EndpointCase.CUSTOM: { + const custom = relaySettings.getCustom()?.toObject(); + const config = relaySettings.getCustom()?.getConfig(); + const connectionConfig = config && convertFromConnectionConfig(config); + return ( + custom && + connectionConfig && { + customTunnelEndpoint: { + ...custom, + config: connectionConfig, + }, + } + ); + } + case grpcTypes.RelaySettings.EndpointCase.NORMAL: { + const normal = relaySettings.getNormal()!; + const grpcLocation = normal.getLocation(); + const location = grpcLocation + ? { only: convertFromLocation(grpcLocation.toObject()) } + : 'any'; + const tunnelProtocol = convertFromTunnelTypeConstraint(normal.getTunnelType()!); + const openvpnConstraints = convertFromOpenVpnConstraints(normal.getOpenvpnConstraints()!); + const wireguardConstraints = convertFromWireguardConstraints( + normal.getWireguardConstraints()!, + ); + + return { + normal: { + location, + tunnelProtocol, + wireguardConstraints, + openvpnConstraints, + }, + }; } - } catch (error) { - throw new ResponseParseError(`Invalid response from generate_wireguard_key ${error}`); } + } else { + return undefined; } +} - public async getWireguardKey(): Promise<IWireguardPublicKey | undefined> { - const response = await this.transport.send('get_wireguard_key'); - try { - return validate(maybe(wireguardPublicKey), response) || undefined; - } catch (error) { - throw new ResponseParseError('Invalid response from get_wireguard_key'); +function convertFromBridgeSettings( + bridgeSettings: grpcTypes.BridgeSettings.AsObject, +): BridgeSettings { + const normalSettings = bridgeSettings.normal; + if (normalSettings) { + const grpcLocation = normalSettings.location; + const location = grpcLocation ? { only: convertFromLocation(grpcLocation) } : 'any'; + return { + normal: { + location, + }, + }; + } + + const customSettings = (settings: ProxySettings): BridgeSettings => { + return { custom: settings }; + }; + + const localSettings = bridgeSettings.local; + if (localSettings) { + return customSettings({ + port: localSettings?.port!, + peer: localSettings?.peer!, + }); + } + + const remoteSettings = bridgeSettings.remote; + if (remoteSettings) { + return customSettings({ + address: remoteSettings?.address!, + auth: { + ...remoteSettings?.auth!, + }, + }); + } + + const shadowsocksSettings = bridgeSettings.shadowsocks!; + return customSettings({ + peer: shadowsocksSettings.peer!, + password: shadowsocksSettings.password!, + cipher: shadowsocksSettings.cipher!, + }); +} + +function convertFromConnectionConfig( + connectionConfig: grpcTypes.ConnectionConfig, +): ConnectionConfig | undefined { + const connectionConfigObject = connectionConfig.toObject(); + switch (connectionConfig.getConfigCase()) { + case grpcTypes.ConnectionConfig.ConfigCase.CONFIG_NOT_SET: + return undefined; + case grpcTypes.ConnectionConfig.ConfigCase.WIREGUARD: + return ( + connectionConfigObject.wireguard && + connectionConfigObject.wireguard.tunnel && + connectionConfigObject.wireguard.peer && { + wireguard: { + ...connectionConfigObject.wireguard, + tunnel: { + privateKey: convertFromWireguardKey( + connectionConfigObject.wireguard.tunnel.privateKey, + ), + addresses: connectionConfigObject.wireguard.tunnel.addressesList, + }, + peer: { + ...connectionConfigObject.wireguard.peer, + addresses: connectionConfigObject.wireguard.peer.allowedIpsList, + publicKey: convertFromWireguardKey(connectionConfigObject.wireguard.peer.publicKey), + }, + }, + } + ); + case grpcTypes.ConnectionConfig.ConfigCase.OPENVPN: { + const [ip, port] = connectionConfigObject.openvpn!.address.split(':'); + return { + openvpn: { + ...connectionConfigObject.openvpn!, + endpoint: { + ip, + port: parseInt(port, 10), + protocol: convertFromTransportProtocol(connectionConfigObject.openvpn!.protocol), + }, + }, + }; } } +} - public async verifyWireguardKey(): Promise<boolean> { - const response = await this.transport.send('verify_wireguard_key'); - try { - return validate(boolean, response); - } catch (error) { - throw new ResponseParseError('Invalid response from verify_wireguard_key'); +function convertFromLocation(location: grpcTypes.RelayLocation.AsObject): RelayLocation { + if (location.hostname) { + return { hostname: [location.country, location.city, location.hostname] }; + } + if (location.city) { + return { city: [location.country, location.city] }; + } + + return { country: location.country }; +} + +function convertFromTunnelOptions(tunnelOptions: grpcTypes.TunnelOptions.AsObject): ITunnelOptions { + return { + openvpn: { + mssfix: tunnelOptions.openvpn!.mssfix, + }, + wireguard: { + mtu: tunnelOptions.wireguard!.mtu, + }, + generic: { + enableIpv6: tunnelOptions.generic!.enableIpv6, + }, + }; +} + +function convertFromDaemonEvent(data: grpcTypes.DaemonEvent): DaemonEvent { + const tunnelState = data.getTunnelState(); + if (tunnelState !== undefined) { + return { tunnelState: convertFromTunnelState(tunnelState)! }; + } + + const settings = data.getSettings(); + if (settings !== undefined) { + return { settings: convertFromSettings(settings)! }; + } + + const relayList = data.getRelayList(); + if (relayList !== undefined) { + return { + relayList: { + countries: relayList + .getCountriesList() + ?.map((country: grpcTypes.RelayListCountry) => + convertFromRelayListCountry(country.toObject()), + ), + }, + }; + } + + const keygenEvent = data.getKeyEvent(); + if (keygenEvent !== undefined) { + return { + wireguardKey: convertFromKeygenEvent(keygenEvent), + }; + } + + return { + appVersionInfo: data.getVersionInfo()!.toObject(), + }; +} + +function convertFromKeygenEvent(data: grpcTypes.KeygenEvent): KeygenEvent { + switch (data.getEvent()) { + case grpcTypes.KeygenEvent.KeygenEvent.TOO_MANY_KEYS: + return 'too_many_keys'; + case grpcTypes.KeygenEvent.KeygenEvent.NEW_KEY: { + const newKey = data.getNewKey(); + return newKey + ? { + newKey: { + created: newKey.getCreated()!.toDate().toISOString(), + key: convertFromWireguardKey(newKey.getKey()), + }, + } + : 'generation_failure'; } + case grpcTypes.KeygenEvent.KeygenEvent.GENERATION_FAILURE: + return 'generation_failure'; } +} - public async getVersionInfo(): Promise<IAppVersionInfo> { - const response = await this.transport.send('get_version_info', [], NETWORK_CALL_TIMEOUT); - try { - return snakeCaseToCamelCase(validate(appVersionInfoSchema, response)); - } catch (error) { - throw new ResponseParseError('Invalid response from get_version_info'); +function convertFromOpenVpnConstraints( + constraints: grpcTypes.OpenvpnConstraints, +): IOpenVpnConstraints { + const port = convertFromConstraint(constraints.getPort()); + let protocol: Constraint<RelayProtocol> = 'any'; + switch (constraints.getProtocol()?.getProtocol()) { + case grpcTypes.TransportProtocol.TCP: + protocol = { only: 'tcp' }; + break; + case grpcTypes.TransportProtocol.UDP: + protocol = { only: 'udp' }; + break; + } + + return { port, protocol }; +} + +function convertFromWireguardConstraints( + constraints: grpcTypes.WireguardConstraints, +): IWireguardConstraints { + const port = convertFromConstraint(constraints.getPort()); + return { port }; +} + +function convertFromTunnelTypeConstraint( + constraint: grpcTypes.TunnelTypeConstraint | undefined, +): Constraint<TunnelProtocol> { + switch (constraint?.getTunnelType()) { + case grpcTypes.TunnelType.WIREGUARD: { + return { only: 'wireguard' }; + } + case grpcTypes.TunnelType.OPENVPN: { + return { only: 'openvpn' }; } + default: { + return 'any'; + } + } +} + +function convertFromConstraint<T>(value: T | undefined): Constraint<T> { + if (value) { + return { only: value }; + } else { + return 'any'; + } +} + +function convertToNormalBridgeSettings( + constraints: IBridgeConstraints, +): grpcTypes.BridgeSettings.BridgeConstraints { + const normalBridgeSettings = new grpcTypes.BridgeSettings.BridgeConstraints(); + normalBridgeSettings.setLocation(convertToLocation(liftConstraint(constraints.location))); + + return normalBridgeSettings; +} + +function convertToLocation( + constraint: RelayLocation | undefined, +): grpcTypes.RelayLocation | undefined { + const location = new grpcTypes.RelayLocation(); + if (constraint && 'hostname' in constraint) { + const [countryCode, cityCode, hostname] = constraint.hostname; + location.setCountry(countryCode); + location.setCity(cityCode); + location.setHostname(hostname); + return location; + } else if (constraint && 'city' in constraint) { + location.setCountry(constraint.city[0]); + location.setCity(constraint.city[1]); + return location; + } else if (constraint && 'country' in constraint) { + location.setCountry(constraint.country); + return location; + } else { + return undefined; + } +} + +function convertToTunnelTypeConstraint( + constraint: Constraint<TunnelType>, +): grpcTypes.TunnelTypeConstraint | undefined { + const grpcConstraint = new grpcTypes.TunnelTypeConstraint(); + + if (constraint !== undefined && constraint !== 'any' && 'only' in constraint) { + switch (constraint.only) { + case 'wireguard': + grpcConstraint.setTunnelType(grpcTypes.TunnelType.WIREGUARD); + return grpcConstraint; + case 'openvpn': + grpcConstraint.setTunnelType(grpcTypes.TunnelType.OPENVPN); + return grpcConstraint; + } + } + return undefined; +} + +function convertToOpenVpnConstraints( + constraints: Partial<IOpenVpnConstraints> | undefined, +): grpcTypes.OpenvpnConstraints | undefined { + const openvpnConstraints = new grpcTypes.OpenvpnConstraints(); + if (constraints) { + const port = liftConstraint(constraints.port); + if (port) { + openvpnConstraints.setPort(port); + } + const protocol = liftConstraint(constraints.protocol); + if (protocol) { + const transportConstraint = new grpcTypes.TransportProtocolConstraint(); + transportConstraint.setProtocol(convertToTransportProtocol(protocol)); + openvpnConstraints.setProtocol(transportConstraint); + } + return openvpnConstraints; + } + + return undefined; +} + +function convertToWireguardConstraints( + constraint: Partial<IWireguardConstraints> | undefined, +): grpcTypes.WireguardConstraints | undefined { + if (constraint) { + const wireguardConstraints = new grpcTypes.WireguardConstraints(); + const port = liftConstraint(constraint.port); + if (port) { + wireguardConstraints.setPort(port); + } + return wireguardConstraints; + } + return undefined; +} + +function convertToTransportProtocol(protocol: RelayProtocol): grpcTypes.TransportProtocol { + switch (protocol) { + case 'udp': + return grpcTypes.TransportProtocol.UDP; + case 'tcp': + return grpcTypes.TransportProtocol.TCP; + } +} + +function ensureExists<T>(value: T | undefined, errorMessage: string): T { + if (value) { + return value; } + throw new ResponseParseError(errorMessage); } diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 53ee113410..3ad81f453d 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -44,12 +44,7 @@ import consumePromise from '../shared/promise'; import { Scheduler } from '../shared/scheduler'; import AccountDataCache from './account-data-cache'; import { getOpenAtLogin, setOpenAtLogin } from './autostart'; -import { - ConnectionObserver, - DaemonRpc, - ResponseParseError, - SubscriptionListener, -} from './daemon-rpc'; +import { ConnectionObserver, DaemonRpc, SubscriptionListener } from './daemon-rpc'; import { InvalidAccountError } from './errors'; import Expectation from './expectation'; import GuiSettings from './gui-settings'; @@ -63,7 +58,7 @@ import WindowController from './window-controller'; const linuxSplitTunneling = process.platform === 'linux' && require('./linux-split-tunneling'); const DAEMON_RPC_PATH = - process.platform === 'win32' ? '//./pipe/Mullvad VPN' : '/var/run/mullvad-vpn'; + process.platform === 'win32' ? 'unix:////./pipe/Mullvad VPN' : 'unix:///var/run/mullvad-vpn'; const AUTO_CONNECT_FALLBACK_DELAY = 6000; @@ -95,7 +90,7 @@ class ApplicationMain { private windowController?: WindowController; private trayIconController?: TrayIconController; - private daemonRpc = new DaemonRpc(); + private daemonRpc = new DaemonRpc(DAEMON_RPC_PATH); private daemonEventListener?: SubscriptionListener<DaemonEvent>; private reconnectBackoff = new ReconnectionBackoff(); private connectedToDaemon = false; @@ -138,7 +133,6 @@ class ApplicationMain { }, wireguard: { mtu: undefined, - fwmark: undefined, }, }, }; @@ -310,7 +304,7 @@ class ApplicationMain { // Unsubscribe the event handler try { if (this.daemonEventListener) { - await this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener); + this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener); log.info('Unsubscribed from the daemon events'); } @@ -326,6 +320,8 @@ class ApplicationMain { if (process.platform === 'darwin' && this.windowController) { this.windowController.window.closable = true; } + + this.daemonRpc.disconnect(); } private detectLocale(): string { @@ -419,7 +415,7 @@ class ApplicationMain { // subscribe to events try { - this.daemonEventListener = await this.subscribeEvents(); + this.daemonEventListener = this.subscribeEvents(); } catch (error) { log.error(`Failed to subscribe: ${error.message}`); @@ -500,6 +496,9 @@ class ApplicationMain { }; private onDaemonDisconnected = (error?: Error) => { + if (this.daemonEventListener) { + this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener); + } // make sure we were connected before to distinguish between a failed attempt to reconnect and // connection loss. const wasConnected = this.connectedToDaemon; @@ -507,6 +506,9 @@ class ApplicationMain { // Reset the daemon event listener since it's going to be invalidated on disconnect this.daemonEventListener = undefined; + // TODO: GRPC doesn't set an error, but without an error, the UI won't be updated + error = error === undefined && wasConnected ? new Error('Connection to daemon lost') : error; + if (wasConnected) { this.connectedToDaemon = false; @@ -529,32 +531,24 @@ class ApplicationMain { } else { log.error(`Failed to connect to daemon: ${error.message}`); } - - this.reconnectToDaemon(); } else { log.info('Disconnected from the daemon'); } }; private connectToDaemon() { - consumePromise(this.daemonRpc.connect({ path: DAEMON_RPC_PATH })); - } - - private reconnectToDaemon() { - this.reconnectBackoff.attempt(() => { - this.connectToDaemon(); - }); + consumePromise(this.daemonRpc.connect()); } private recoverFromBootstrapError(_error?: Error) { // Attempt to reconnect to daemon if the program fails to fetch settings, tunnel state or // subscribe for RPC events. - this.daemonRpc.disconnect(); - - this.reconnectToDaemon(); + if (this.daemonEventListener) { + this.daemonRpc.unsubscribeDaemonEventListener(this.daemonEventListener); + } } - private async subscribeEvents(): Promise<SubscriptionListener<DaemonEvent>> { + private subscribeEvents(): SubscriptionListener<DaemonEvent> { const daemonEventListener = new SubscriptionListener( (daemonEvent: DaemonEvent) => { if ('tunnelState' in daemonEvent) { @@ -575,14 +569,11 @@ class ApplicationMain { }, (error: Error) => { log.error(`Cannot deserialize the daemon event: ${error.message}`); - - if (error instanceof ResponseParseError && error.validationError) { - log.error(error.validationError.message); - } + log.error(error.stack); }, ); - await this.daemonRpc.subscribeDaemonEventListener(daemonEventListener); + this.daemonRpc.subscribeDaemonEventListener(daemonEventListener); return daemonEventListener; } diff --git a/gui/src/renderer/components/RedeemVoucher.tsx b/gui/src/renderer/components/RedeemVoucher.tsx index daf5074603..7ca83c43d1 100644 --- a/gui/src/renderer/components/RedeemVoucher.tsx +++ b/gui/src/renderer/components/RedeemVoucher.tsx @@ -86,7 +86,7 @@ export function RedeemVoucherContainer(props: IRedeemVoucherProps) { if (response.type === 'success') { setValue(''); closeScheduler.schedule(() => { - updateAccountExpiry(response.new_expiry); + updateAccountExpiry(response.newExpiry); onSuccess?.(); }, 1000); } else { diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index 1f1779a4b0..78b6f60c17 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -46,14 +46,14 @@ export type ErrorStateCause = export type AfterDisconnect = 'nothing' | 'block' | 'reconnect'; -export type TunnelType = 'wireguard' | 'openvpn'; +export type TunnelType = 'any' | 'wireguard' | 'openvpn'; export function tunnelTypeToString(tunnel: TunnelType): string { switch (tunnel) { case 'wireguard': return 'WireGuard'; case 'openvpn': return 'OpenVPN'; - default: + case 'any': return ''; } } @@ -235,14 +235,16 @@ export interface IOpenVpnTunnelData { } export interface IWireguardTunnelData { - // Port ranges are an array of pairs, such as [[53,53], [10_000, 60_000]], - // which in this case translates that the specific tunnel can be connected on - // port 53 and ports 10'000 through 60'000. - portRanges: Array<[number, number]>; + portRanges: Array<IPortRange>; // Public key of the tunnel. publicKey: string; } +export interface IPortRange { + first: number; + last: number; +} + export interface IShadowsocksEndpointData { port: number; cipher: string; @@ -256,8 +258,6 @@ export interface ITunnelOptions { }; wireguard: { mtu?: number; - // Only relevant on Linux - fwmark?: number; }; generic: { enableIpv6: boolean; @@ -330,7 +330,7 @@ export interface ISocketAddress { } export type VoucherResponse = - | { type: 'success'; new_expiry: string } + | { type: 'success'; newExpiry: string; secondsAdded: number } | { type: 'invalid' | 'already_used' | 'error' }; export enum VoucherErrorCode { diff --git a/gui/tasks/scripts.js b/gui/tasks/scripts.js index 82896dac21..5ee33a9071 100644 --- a/gui/tasks/scripts.js +++ b/gui/tasks/scripts.js @@ -1,4 +1,5 @@ -const { src, dest } = require('gulp'); +const { exec } = require('child_process'); +const { src, dest, series } = require('gulp'); const ts = require('gulp-typescript'); const inject = require('gulp-inject-string'); const TscWatchClient = require('tsc-watch/client'); @@ -25,7 +26,13 @@ function compileScripts() { .pipe(dest('build')); } +function buildProto(callback) { + exec('bash ./scripts/build-proto.sh', (err) => callback(err)); +} + compileScripts.displayName = 'compile-scripts'; +buildProto.displayName = 'build-proto'; exports.build = compileScripts; +exports.buildProto = buildProto; exports.makeWatchCompiler = makeWatchCompiler; |
