diff --git a/.eslintrc.common.json b/.eslintrc.common.json index 213d186f7b..64eac1d9fb 100644 --- a/.eslintrc.common.json +++ b/.eslintrc.common.json @@ -1,6 +1,7 @@ { "plugins": [ - "@typescript-eslint" + "@typescript-eslint", + "unused-imports" ], "extends": [ "eslint:recommended", @@ -11,7 +12,7 @@ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-unused-vars": "off", + "unused-imports/no-unused-imports": "error", "max-len": [ "error", { diff --git a/.vscode/launch.json b/.vscode/launch.json index bc4449a27e..b58bce6a93 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,7 +32,11 @@ "hidden": false, "group": "Debug", "order": 20 - } + }, + "skipFiles": [ + "/**", + "${workspaceFolder}/node_modules/**" + ] }, { "name": "Attach: Renderer", @@ -41,14 +45,17 @@ "port": 9222, "sourceMaps": true, "trace": "verbose", - "url": "file://*", "webRoot": "${workspaceRoot}/build", "timeout": 30000, "presentation": { "hidden": false, "group": "Debug", "order": 30 - } + }, + "skipFiles": [ + "/**", + "${workspaceFolder}/node_modules/**" + ] }, { "name": "Test: Client", @@ -68,7 +75,12 @@ "hidden": false, "group": "Test", "order": 10 - } + }, + "skipFiles": [ + "/**", + "${workspaceFolder}/node_modules/**", + "${workspaceFolder}/test/client/**" + ] } ], "compounds": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e5aa1f06..96f903fe6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 2.10.0 + +[All items](https://github.com/Azure/BatchExplorer/milestone/44?closed=1) + +### feature + +* Migrates authentication to MSAL [\#2331](https://github.com/Azure/BatchExplorer/issues/2331) + +### other + +* Switch to Typescript in upload-to-storage [\#2308](https://github.com/Azure/BatchExplorer/issues/2308) + # 2.9.0 [All items](https://github.com/Azure/BatchExplorer/milestone/43?closed=1) diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 61e890bcf1..e962c254b2 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -18,50 +18,51 @@ Microsoft reserves all other rights not expressly granted under this agreement, 8. @angular/platform-browser-dynamic(https://github.com/angular/angular#readme) - MIT 9. @angular/platform-server(https://github.com/angular/angular#readme) - MIT 10. @angular/router(https://github.com/angular/angular/tree/master/packages/router) - MIT -11. @azure/storage-blob(https://github.com/Azure/azure-sdk-for-js#readme) - MIT -12. @types/keytar(https://github.com/node-keytar) - MIT -13. applicationinsights(https://github.com/Microsoft/ApplicationInsights-node.js#readme) - MIT -14. azure-storage(http://github.com/Azure/azure-storage-node) - Apache-2.0 -15. chart.js(https://www.chartjs.org) - MIT -16. chokidar(https://github.com/paulmillr/chokidar) - MIT -17. commander(https://github.com/tj/commander.js#readme) - MIT -18. d3(https://d3js.org) - BSD-3-Clause -19. decode-uri-component(https://github.com/SamVerschueren/decode-uri-component#readme) - MIT -20. download(https://github.com/kevva/download#readme) - MIT -21. electron-updater(https://github.com/electron-userland/electron-builder) - MIT -22. element-resize-detector(https://github.com/wnr/element-resize-detector) - MIT -23. extract-zip(https://github.com/maxogden/extract-zip#readme) - BSD-2-Clause -24. focus-visible(https://github.com/WICG/focus-visible) - W3C -25. font-awesome(http://fontawesome.io/) - (OFL-1.1 AND MIT) -26. get-proxy-settings(https://github.com/Azure/get-proxy-settings) - MIT -27. glob(https://github.com/isaacs/node-glob#readme) - ISC -28. hammerjs(http://hammerjs.github.io/) - MIT -29. https-proxy-agent(https://github.com/TooTallNate/node-https-proxy-agent#readme) - MIT -30. immutable(https://facebook.github.com/immutable-js) - MIT -31. inflection(https://github.com/dreamerslab/node.inflection#readme) - MIT -32. js-yaml(https://github.com/nodeca/js-yaml) - MIT -33. jschardet(https://github.com/aadsm/jschardet#readme) - LGPL-2.1+ -34. keytar(http://atom.github.io/node-keytar) - MIT -35. load-json-file(https://github.com/sindresorhus/load-json-file#readme) - MIT -36. luxon(https://github.com/moment/luxon#readme) - MIT -37. make-dir(https://github.com/sindresorhus/make-dir#readme) - MIT -38. node-abi(https://github.com/lgeiger/node-abi#readme) - MIT -39. node-forge(https://github.com/digitalbazaar/forge) - (BSD-3-Clause OR GPL-2.0) -40. patternomaly(https://github.com/ashiguruma/patternomaly#readme) - MIT -41. reflect-metadata(http://rbuckton.github.io/reflect-metadata) - Apache-2.0 -42. rxjs(https://github.com/ReactiveX/RxJS) - Apache-2.0 -43. strip-json-comments(https://github.com/sindresorhus/strip-json-comments#readme) - MIT -44. tinycolor2(https://github.com/TinyColor) - MIT -45. winston(https://github.com/winstonjs/winston#readme) - MIT -46. winston-daily-rotate-file(https://github.com/winstonjs/winston-daily-rotate-file#readme) - MIT -47. write-file-webpack-plugin(https://github.com/gajus/write-file-webpack-plugin#readme) - BSD-3-Clause -48. xml2js(https://github.com/Leonidas-from-XIV/node-xml2js) - MIT -49. zone.js(https://github.com/angular/angular#readme) - MIT -50. websockets(https://github.com/aaugustin/websockets) - BSD-3-Clause -51. azure-batch-cli-extensions(https://github.com/Azure/azure-batch-cli-extensions) - MIT -52. node.js(https://nodejs.org/en/) - MIT -53. python(https://www.python.org/) - PSF -54. Electron(https://electronjs.org/) - electron +11. @azure/msal-node(https://github.com/AzureAD/microsoft-authentication-library-for-js#readme) - MIT +12. @azure/storage-blob(https://github.com/Azure/azure-sdk-for-js#readme) - MIT +13. @types/keytar(https://github.com/node-keytar) - MIT +14. applicationinsights(https://github.com/microsoft/ApplicationInsights-node.js#readme) - MIT +15. azure-storage(http://github.com/Azure/azure-storage-node) - Apache-2.0 +16. chart.js(https://www.chartjs.org) - MIT +17. chokidar(https://github.com/paulmillr/chokidar) - MIT +18. commander(https://github.com/tj/commander.js#readme) - MIT +19. d3(https://d3js.org) - BSD-3-Clause +20. decode-uri-component(https://github.com/SamVerschueren/decode-uri-component#readme) - MIT +21. download(https://github.com/kevva/download#readme) - MIT +22. electron-updater(https://github.com/electron-userland/electron-builder) - MIT +23. element-resize-detector(https://github.com/wnr/element-resize-detector) - MIT +24. extract-zip(https://github.com/maxogden/extract-zip#readme) - BSD-2-Clause +25. focus-visible(https://github.com/WICG/focus-visible) - W3C +26. font-awesome(http://fontawesome.io/) - (OFL-1.1 AND MIT) +27. get-proxy-settings(https://github.com/Azure/get-proxy-settings) - MIT +28. glob(https://github.com/isaacs/node-glob#readme) - ISC +29. hammerjs(http://hammerjs.github.io/) - MIT +30. https-proxy-agent(https://github.com/TooTallNate/node-https-proxy-agent#readme) - MIT +31. immutable(https://facebook.github.com/immutable-js) - MIT +32. inflection(https://github.com/dreamerslab/node.inflection#readme) - MIT +33. js-yaml(https://github.com/nodeca/js-yaml) - MIT +34. jschardet(https://github.com/aadsm/jschardet#readme) - LGPL-2.1+ +35. keytar(http://atom.github.io/node-keytar) - MIT +36. load-json-file(https://github.com/sindresorhus/load-json-file#readme) - MIT +37. luxon(https://github.com/moment/luxon#readme) - MIT +38. make-dir(https://github.com/sindresorhus/make-dir#readme) - MIT +39. node-abi(https://github.com/lgeiger/node-abi#readme) - MIT +40. node-forge(https://github.com/digitalbazaar/forge) - (BSD-3-Clause OR GPL-2.0) +41. patternomaly(https://github.com/ashiguruma/patternomaly#readme) - MIT +42. reflect-metadata(http://rbuckton.github.io/reflect-metadata) - Apache-2.0 +43. rxjs(https://github.com/ReactiveX/RxJS) - Apache-2.0 +44. strip-json-comments(https://github.com/sindresorhus/strip-json-comments#readme) - MIT +45. tinycolor2(https://github.com/bgrins/TinyColor#readme) - MIT +46. winston(https://github.com/winstonjs/winston#readme) - MIT +47. winston-daily-rotate-file(https://github.com/winstonjs/winston-daily-rotate-file#readme) - MIT +48. write-file-webpack-plugin(https://github.com/gajus/write-file-webpack-plugin#readme) - BSD-3-Clause +49. xml2js(https://github.com/Leonidas-from-XIV/node-xml2js) - MIT +50. zone.js(https://github.com/angular/angular#readme) - MIT +51. websockets(https://github.com/aaugustin/websockets) - BSD-3-Clause +52. azure-batch-cli-extensions(https://github.com/Azure/azure-batch-cli-extensions) - MIT +53. node.js(https://nodejs.org/en/) - MIT +54. python(https://www.python.org/) - PSF +55. Electron(https://electronjs.org/) - electron ============================================================ Start license for @angular/animations @@ -382,6 +383,9 @@ THE SOFTWARE. End license for @angular/router ============================================================ +============================================================ + Start license for @azure/msal-node +------------------------------------------------------------ ============================================================ Start license for @azure/storage-blob ------------------------------------------------------------ @@ -416,6 +420,31 @@ SOFTWARE. ============================================================ Start license for applicationinsights ------------------------------------------------------------ +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------ + End license for applicationinsights +============================================================ + ============================================================ Start license for azure-storage ------------------------------------------------------------ @@ -2370,6 +2399,30 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ============================================================ Start license for tinycolor2 ------------------------------------------------------------ +Copyright (c), Brian Grinstead, http://briangrinstead.com + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------ + End license for tinycolor2 +============================================================ + ============================================================ Start license for winston ------------------------------------------------------------ @@ -2490,7 +2543,7 @@ THE SOFTWARE. ============================================================ Start license for websockets ------------------------------------------------------------ -Copyright (c) 2013-2019 Aymeric Augustin and contributors. +Copyright (c) 2013-2021 Aymeric Augustin and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -3879,29 +3932,6 @@ The externally maintained libraries used by Node.js are: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -- node-inspect, located at deps/node-inspect, is licensed as follows: - """ - Copyright Node.js contributors. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to - deal in the Software without restriction, including without limitation the - rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - IN THE SOFTWARE. - """ - - large_pages, located at src/large_pages, is licensed as follows: """ Copyright (C) 2018 Intel Corporation diff --git a/docs/testing.md b/docs/testing.md index 2138d1c9c3..254b9ad0e1 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -23,7 +23,7 @@ fdescribe("MyModuleB", () => { }); ``` -**Note: TSLint will scan for `fdescribe` and `fit` so you don't forget one when creating a PR** +**Note: ESLint will scan for `fdescribe` and `fit` so you don't forget one when creating a PR** ## Test the client diff --git a/karma.conf.js b/karma.conf.js index cf595c652b..680d8fd31c 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -3,7 +3,7 @@ const webpackConfig = require("./config/webpack.config.test"); process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true"; // Only enable coverage if env is defined(So we don't enable it in watch mode as it duplicate logs) -const coverageReporters = process.env.COVERAGE ? ["coverage", "remap-coverage", "junit"] : []; +const coverageReporters = process.env.COVERAGE ? ["coverage", "junit"] : []; // Karma config for testing the code running the browser environemnt. // For the client testing use the mocha command line. @@ -87,13 +87,12 @@ module.exports = function(config) { reportSlowerThan: 200, }, coverageReporter: { - type: "in-memory" - }, - remapCoverageReporter: { - "text-summary": null, - json: "./coverage/coverage.json", - html: "./coverage/html", - cobertura: "./coverage/cobertura.xml", + dir: "./coverage", + reporters: [ + { type: "json", subdir: ".", file: "coverage.json" }, + { type: "html", subdir: "./html" }, + { type: "cobertura", subdir: ".", file: "cobertura.xml" }, + ] }, // Can't enable yet has a conflict in dependency with azure-storage junitReporter: { diff --git a/package-lock.json b/package-lock.json index 5762ddb3ea..cc58709420 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "batch-explorer", - "version": "2.9.0", + "version": "2.10.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,36 +10,6 @@ "integrity": "sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA==", "dev": true }, - "@angular-devkit/core": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.13.tgz", - "integrity": "sha512-bwehVRsva9OWfh/yuEh9VU+0Gr1T7DHJLe8tpZk/VsIkGOD0IszEPZOIEK23bg32yiff9bh6qJEPMA7ZBYEQHg==", - "dev": true, - "requires": { - "ajv": "6.12.3", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.5.4", - "source-map": "0.7.3" - }, - "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, "@angular-eslint/eslint-plugin": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-1.2.0.tgz", @@ -67,6 +37,18 @@ "dev": true, "requires": { "eslint-scope": "^5.1.0" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + } } }, "@angular/animations": { @@ -200,6 +182,42 @@ "xml2js": "^0.4.19" } }, + "@azure/msal-common": { + "version": "4.1.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/@azure/msal-common/-/msal-common-4.1.1.tgz", + "integrity": "sha1-w9LEO4B40oCYLETzsnU7wZ6X1mw=", + "requires": { + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@azure/msal-node": { + "version": "1.0.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/@azure/msal-node/-/msal-node-1.0.1.tgz", + "integrity": "sha1-lPALj/RPsX0d0YQeuvtxg/Y5ol4=", + "requires": { + "@azure/msal-common": "^4.0.2", + "axios": "^0.21.1", + "jsonwebtoken": "^8.5.1", + "uuid": "^8.3.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha1-gNW1ztJxu5r2xEXyGhoExgbO++I=" + } + } + }, "@azure/storage-blob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-10.5.0.tgz", @@ -258,6 +276,31 @@ "@babel/highlight": "^7.12.13" } }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -267,23 +310,23 @@ } }, "@babel/eslint-parser": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.13.8.tgz", - "integrity": "sha512-XewKkiyukrGzMeqToXJQk6hjg2veI9SNQElGzAoAjKxYCLbgcVX4KA2WhoyqMon9N4RMdCZhNTJNOBcp9spsiw==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.13.14.tgz", + "integrity": "sha512-I0HweR36D73Ibn/FfrRDMKlMqJHFwidIUgYdMpH+aXYuQC+waq59YaJ6t9e9N36axJ82v1jR041wwqDrDXEwRA==", "dev": true, "requires": { - "eslint-scope": "5.1.0", + "eslint-scope": "^5.1.0", "eslint-visitor-keys": "^1.3.0", "semver": "^6.3.0" }, "dependencies": { "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, @@ -306,6 +349,22 @@ "source-map": "^0.5.0" }, "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -335,6 +394,24 @@ "@babel/helper-get-function-arity": "^7.12.13", "@babel/template": "^7.12.13", "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-get-function-arity": { @@ -344,6 +421,24 @@ "dev": true, "requires": { "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-member-expression-to-functions": { @@ -353,6 +448,24 @@ "dev": true, "requires": { "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-module-imports": { @@ -362,6 +475,24 @@ "dev": true, "requires": { "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-module-transforms": { @@ -379,6 +510,26 @@ "@babel/traverse": "^7.13.0", "@babel/types": "^7.13.0", "lodash": "^4.17.19" + }, + "dependencies": { + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + } + } + } } }, "@babel/helper-optimise-call-expression": { @@ -388,6 +539,24 @@ "dev": true, "requires": { "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-replace-supers": { @@ -400,6 +569,24 @@ "@babel/helper-optimise-call-expression": "^7.12.13", "@babel/traverse": "^7.13.0", "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-simple-access": { @@ -409,6 +596,24 @@ "dev": true, "requires": { "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-split-export-declaration": { @@ -418,6 +623,24 @@ "dev": true, "requires": { "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-validator-identifier": { @@ -441,6 +664,24 @@ "@babel/template": "^7.12.13", "@babel/traverse": "^7.13.0", "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/highlight": { @@ -498,6 +739,22 @@ "requires": { "@babel/highlight": "^7.12.13" } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } } } }, @@ -527,6 +784,31 @@ "@babel/highlight": "^7.12.13" } }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/types": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", + "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -536,14 +818,22 @@ } }, "@babel/types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz", - "integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } } }, "@dabh/diagnostics": { @@ -564,12 +854,38 @@ "requires": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + } } }, "@electron/get": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.4.tgz", - "integrity": "sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.2.tgz", + "integrity": "sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg==", "dev": true, "requires": { "debug": "^4.1.1", @@ -579,7 +895,7 @@ "global-tunnel-ng": "^2.7.1", "got": "^9.6.0", "progress": "^2.0.3", - "semver": "^6.2.0", + "sanitize-filename": "^1.6.2", "sumchecker": "^3.0.1" }, "dependencies": { @@ -605,9 +921,9 @@ }, "dependencies": { "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { "pump": "^3.0.0" @@ -621,6 +937,15 @@ } } }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -632,6 +957,15 @@ "universalify": "^0.1.0" } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -657,6 +991,12 @@ "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, + "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 + }, "normalize-url": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", @@ -684,6 +1024,15 @@ "fs-extra": "^9.0.1" }, "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -715,15 +1064,15 @@ } }, "@eslint/eslintrc": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", - "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", - "globals": "^12.1.0", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", @@ -751,6 +1100,12 @@ } } }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, "@malept/cross-spawn-promise": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", @@ -804,62 +1159,113 @@ } }, "@ngtools/webpack": { - "version": "9.1.13", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.13.tgz", - "integrity": "sha512-mTygcNgr58Mpv+WVrkXe3QXZJO5RKUEDcMoj0bscBp9G62MiMsRKnkDjb5GSXXnSGZb5GOlzdVwayib1O5y3uQ==", + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.12.tgz", + "integrity": "sha512-lypMXIq5oxBMsoDu/VOa1yUmmXthhxkCJa8LG0ZohfnbwhmZvz3SAW7omBGuVrb5cVIfLCkaRCSnQ1MNc6ULXw==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.13", + "@angular-devkit/core": "9.1.12", "enhanced-resolve": "4.1.1", "rxjs": "6.5.4", "webpack-sources": "1.4.3" }, "dependencies": { - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "@angular-devkit/core": { + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.12.tgz", + "integrity": "sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA==", "dev": true, "requires": { - "tslib": "^1.9.0" + "ajv": "6.12.3", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", + "source-map": "0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } } - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.4", - "run-parallel": "^1.1.9" - } - }, + }, + "enhanced-resolve": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + } + }, + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.4", + "@nodelib/fs.scandir": "2.1.3", "fastq": "^1.6.0" } }, "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", "dev": true, "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "mkdirp": "^1.0.4" }, "dependencies": { "mkdirp": { @@ -867,15 +1273,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } } } }, @@ -893,16 +1290,6 @@ "@babel/core": ">=7.9.0" } }, - "@stylelint/postcss-markdown": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", - "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", - "dev": true, - "requires": { - "remark": "^13.0.0", - "unist-util-find-all-after": "^3.0.2" - } - }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -925,9 +1312,9 @@ } }, "@types/chart.js": { - "version": "2.9.31", - "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.31.tgz", - "integrity": "sha512-hzS6phN/kx3jClk3iYqEHNnYIRSi4RZrIGJ8CDLjgatpHoftCezvC44uqB3o3OUm9ftU1m7sHG8+RLyPTlACrA==", + "version": "2.9.24", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.24.tgz", + "integrity": "sha512-AQI7X+ow3SaONl44JrHoL/5B+lCsJyG31UHZ5RP98Uh15hI/zjEkDsAb4EIm4P9TGfNhZLXw/nMc5w0u10+/fQ==", "dev": true, "requires": { "moment": "^2.10.2" @@ -942,6 +1329,24 @@ "colors": "*" } }, + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", + "dev": true + }, + "@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "dev": true + }, "@types/d3": { "version": "5.16.4", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-5.16.4.tgz", @@ -1554,6 +1959,25 @@ "tsutils": "^3.17.1" } }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -1598,6 +2022,16 @@ "@typescript-eslint/types": "4.3.0", "eslint-visitor-keys": "^2.0.0" } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } } } }, @@ -1634,6 +2068,15 @@ "tsutils": "^3.17.1" } }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -1695,6 +2138,15 @@ "eslint-visitor-keys": "^2.0.0" } }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -1757,9 +2209,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -2003,12 +2455,6 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -2039,12 +2485,6 @@ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -2086,12 +2526,6 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, "angular2-template-loader": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz", @@ -2176,30 +2610,12 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, - "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-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", "dev": true }, - "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": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -2214,12 +2630,6 @@ "color-convert": "^1.9.0" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true - }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -2329,12 +2739,6 @@ } } }, - "app-root-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", - "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", - "dev": true - }, "append-transform": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", @@ -2376,9 +2780,9 @@ } }, "archiver": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.2.0.tgz", - "integrity": "sha512-QEAKlgQuAtUxKeZB9w5/ggKXh21bZS+dzzuQ0RPBC20qtDCbTyzqmisoeJP46MP39fg4B4IcyvR+yeyEBdblsQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", "dev": true, "requires": { "archiver-utils": "^2.1.0", @@ -2386,8 +2790,8 @@ "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.0.0", - "tar-stream": "^2.1.4", - "zip-stream": "^4.0.4" + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" }, "dependencies": { "async": { @@ -2397,9 +2801,9 @@ "dev": true }, "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "dev": true, "requires": { "buffer": "^5.5.0", @@ -2524,18 +2928,6 @@ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -2555,12 +2947,6 @@ "is-string": "^1.0.5" } }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2590,12 +2976,6 @@ "es-abstract": "^1.18.0-next.1" } }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2689,12 +3069,6 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true - }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -2814,6 +3188,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axios": { + "version": "0.21.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/axios/-/axios-0.21.1.tgz", + "integrity": "sha1-IlY0gZYvTWvemnbVFu8OXTwJsrg=", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -3054,12 +3436,6 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -3198,12 +3574,6 @@ } } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -3513,16 +3883,36 @@ } }, "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", - "node-releases": "^1.1.70" + "node-releases": "^1.1.71" + }, + "dependencies": { + "caniuse-lite": { + "version": "1.0.30001228", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", + "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.738", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.738.tgz", + "integrity": "sha512-vCMf4gDOpEylPSLPLSwAEsz+R3ShP02Y3cAKMZvTqule3XcPp7tgc/0ESI7IS6ZeyBlGClE50N53fIOkcIVnpw==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + } } }, "buffer": { @@ -3559,6 +3949,11 @@ "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -3719,6 +4114,12 @@ } } }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true + }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -3866,24 +4267,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - } - } - }, "caniuse-lite": { "version": "1.0.30001196", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001196.tgz", @@ -4257,50 +4640,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "codelyzer": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.2.tgz", - "integrity": "sha512-jB4FZ1Sx7kZhvZVdf+N2BaKTdrrNZOL0Bj10RRfrhHrb3zEvXjJvvq298JPMJAiyiCS/v4zs1QlGU0ip7xGqeA==", - "dev": true, - "requires": { - "app-root-path": "^2.2.1", - "aria-query": "^3.0.0", - "axobject-query": "2.0.2", - "css-selector-tokenizer": "^0.7.1", - "cssauron": "^1.4.0", - "damerau-levenshtein": "^1.0.4", - "semver-dsl": "^1.0.1", - "source-map": "^0.5.7", - "sprintf-js": "^1.1.2" - }, - "dependencies": { - "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" - } - }, - "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -4420,24 +4759,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, "compress-commons": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.0.tgz", @@ -4888,6 +5215,16 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", @@ -5116,16 +5453,6 @@ "nth-check": "^1.0.2" } }, - "css-selector-tokenizer": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", - "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2" - } - }, "css-shorthand-properties": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", @@ -5144,30 +5471,12 @@ "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", "dev": true }, - "cssauron": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", - "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", - "dev": true, - "requires": { - "through": "X.X.X" - } - }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -5459,12 +5768,6 @@ "d3-transition": "1" } }, - "damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", - "dev": true - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5485,16 +5788,6 @@ "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", "dev": true }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - } - }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -6067,9 +6360,9 @@ "dev": true }, "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", "dev": true, "requires": { "ip": "^1.1.0", @@ -6234,6 +6527,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha1-rg8PothQRe8UqBfao86azQSJ5b8=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "edge-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", @@ -6281,9 +6582,9 @@ } }, "electron": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.4.4.tgz", - "integrity": "sha512-dcPlTrMWQu5xuSm6sYV42KK/BRIqh3erM8v/WtZqaDmG7pkCeJpvw26Dgbqhdt78XmqqGiN96giEe6A3S9vpAQ==", + "version": "9.4.0", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/electron/-/electron-9.4.0.tgz", + "integrity": "sha1-w8YH41mCJt26r/i6vN/6i7IhCTY=", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -6396,6 +6697,15 @@ "extract-zip": "^2.0.0" }, "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -6508,12 +6818,6 @@ } } }, - "electron-to-chromium": { - "version": "1.3.682", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.682.tgz", - "integrity": "sha512-zok2y37qR00U14uM6qBz/3iIjWHom2eRfC2S1StA0RslP7x34jX+j4mxv80t8OEOHLJPVG54ZPeaFxEI7gPrwg==", - "dev": true - }, "electron-updater": { "version": "4.3.8", "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.8.tgz", @@ -6647,77 +6951,38 @@ } }, "engine.io": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", - "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", + "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", "ws": "~7.4.2" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "engine.io-client": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.1.tgz", - "integrity": "sha512-oVu9kBkGbcggulyVF0kz6BV3ganqUeqXvD79WOFKa+11oK692w1NyFkuEj4xrkFRpZhn92QOqTk4RQq5LiBXbQ==", - "dev": true, - "requires": { - "component-emitter": "~1.3.0", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.2.0", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "ws": "~7.4.2", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true } } }, "engine.io-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", - "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", "dev": true, "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.4", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "base64-arraybuffer": "0.1.4" } }, "enhanced-resolve": { @@ -6867,12 +7132,6 @@ "ext": "^1.1.2" } }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -6890,106 +7149,32 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, "eslint": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz", - "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", + "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.0", + "@eslint/eslintrc": "^0.4.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", @@ -6997,7 +7182,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.20", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -7006,7 +7191,7 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^6.0.4", + "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -7021,9 +7206,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -7050,12 +7235,45 @@ "which": "^2.0.1" } }, + "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 + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "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 + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -7063,9 +7281,9 @@ "dev": true }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -7086,6 +7304,28 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -7101,6 +7341,34 @@ "has-flag": "^4.0.0" } }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7244,6 +7512,15 @@ "spdx-expression-parse": "^3.0.1" }, "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", @@ -7271,25 +7548,33 @@ } }, "eslint-plugin-unicorn": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-27.0.0.tgz", - "integrity": "sha512-uUvlueTa4SpkvLjbkqx08JbB0tY6XxOAa8vlfwbTzITfVNy3go3QzPCus49fO5M/mfooOuraIDVkaqan/pLAHg==", + "version": "31.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-31.0.0.tgz", + "integrity": "sha512-HR3gI4ANtV8A+0FLAaxjBD/G5J3PWBo+7OswyGeK5nylGqtKLJVbnPksIkBgmVg+SFpxu5MnjaxQQI+9KjyVAg==", "dev": true, "requires": { - "ci-info": "^2.0.0", + "ci-info": "^3.1.1", "clean-regexp": "^1.0.0", - "eslint-template-visitor": "^2.2.2", + "eslint-template-visitor": "^2.3.2", "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", "import-modules": "^2.1.0", - "lodash": "^4.17.20", + "is-builtin-module": "^3.1.0", + "lodash": "^4.17.21", "pluralize": "^8.0.0", "read-pkg-up": "^7.0.1", - "regexp-tree": "^0.1.22", + "regexp-tree": "^0.1.23", "reserved-words": "^0.1.2", "safe-regex": "^2.1.1", - "semver": "^7.3.4" + "semver": "^7.3.5" }, "dependencies": { + "ci-info": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz", + "integrity": "sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ==", + "dev": true + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -7301,9 +7586,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "locate-path": { @@ -7391,9 +7676,9 @@ } }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -7401,6 +7686,21 @@ } } }, + "eslint-plugin-unused-imports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-1.1.1.tgz", + "integrity": "sha512-EApvRx9Q3XQI96Tg7xPPqY6OuOy95wWMXAtc8RrwdIRk9bv8vkJKwOVoE4HeWJOQhHeHcI8lUbOqmup/PxjfOw==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, + "eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -7442,9 +7742,9 @@ } }, "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "espree": { @@ -7969,12 +8269,6 @@ "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, "fastq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", @@ -8320,8 +8614,7 @@ "follow-redirects": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "dev": true + "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" }, "font-awesome": { "version": "4.7.0", @@ -8529,15 +8822,6 @@ "readable-stream": "^2.0.0" } }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -8689,12 +8973,6 @@ "npm-conf": "~1.1.3" } }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -8822,12 +9100,20 @@ } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" + }, + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, "globalthis": { @@ -8941,19 +9227,6 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -9006,29 +9279,6 @@ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -9569,12 +9819,6 @@ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -9747,6 +9991,15 @@ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", "dev": true }, + "is-builtin-module": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz", + "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==", + "dev": true, + "requires": { + "builtin-modules": "^3.0.0" + } + }, "is-callable": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", @@ -9804,9 +10057,9 @@ } }, "is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true }, "is-extendable": { @@ -10010,12 +10263,6 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -10043,9 +10290,9 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", - "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", "dev": true }, "isexe": { @@ -10065,76 +10312,6 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "istanbul-instrumenter-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", @@ -10261,6 +10438,15 @@ "source-map": "^0.6.1" }, "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "istanbul-lib-coverage": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", @@ -10468,21 +10654,64 @@ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz", "integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=" }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha1-AOceC431TCEhofJhN98igGc7zA0=", "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/semver/-/semver-5.7.1.tgz", + "integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=" + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha1-dDwymFy56YZVUw1TZBtmyGRbA5o=", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/jws/-/jws-3.2.2.tgz", + "integrity": "sha1-ABCZ82OUaMlBQADpmZX6UvtHgwQ=", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } }, "karma": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/karma/-/karma-5.2.3.tgz", - "integrity": "sha512-tHdyFADhVVPBorIKCX8A37iLHxc6RBRphkSoQ+MLKdAtFn1k97tD8WUGi1KlEtDZKL3hui0qhsY9HXUfSNDYPQ==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.2.tgz", + "integrity": "sha512-fo4Wt0S99/8vylZMxNj4cBFyOBBnC1bewZ0QOlePij/2SZVWxqbyLeIddY13q6URa2EpLRW8ixvFRUMjkmo1bw==", "dev": true, "requires": { "body-parser": "^1.19.0", @@ -10503,200 +10732,154 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^2.3.0", + "socket.io": "^3.1.0", "source-map": "^0.6.1", "tmp": "0.2.1", - "ua-parser-js": "0.7.22", - "yargs": "^15.3.1" + "ua-parser-js": "^0.7.23", + "yargs": "^16.1.1" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "glob": "^7.1.3" } }, - "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==", + "ua-parser-js": { + "version": "0.7.28", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", + "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", + "dev": true + } + } + }, + "karma-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "requires": { + "which": "^1.2.1" + } + }, + "karma-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.3.tgz", + "integrity": "sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.1", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "color-name": "~1.1.4" + "ms": "^2.1.1" } }, - "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==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "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" - } - }, - "is-fullwidth-code-point": { + "istanbul-lib-coverage": { "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==", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.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==", + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" } }, - "path-exists": { + "istanbul-lib-source-maps": { "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 - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { - "glob": "^7.1.3" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" } }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, - "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==", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "semver": "^6.0.0" } }, - "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "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 }, - "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" - } + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "has-flag": "^4.0.0" } } } }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "karma-coverage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-1.1.2.tgz", - "integrity": "sha512-eQawj4Cl3z/CjxslYy9ariU4uDh7cCNFZHNWXWRpl0pNeblY/4wHR7M7boTYXWrn9bY0z2pZmr11eKje/S/hIw==", - "dev": true, - "requires": { - "dateformat": "^1.0.6", - "istanbul": "^0.4.0", - "lodash": "^4.17.0", - "minimatch": "^3.0.0", - "source-map": "^0.5.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, "karma-electron": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/karma-electron/-/karma-electron-6.3.3.tgz", - "integrity": "sha512-1+HCmhBQKZBWxg6nJn4Vp1AUrNaYCRrnl08vZ4nekF9PbQQ4hbz8sWQotSx0WCLtmocDzTsUJSdAtyoWvC7UXQ==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/karma-electron/-/karma-electron-6.3.4.tgz", + "integrity": "sha512-JqoHTWUs5ZcTqB46uGtZ/L1CadL+J3TGGpjH02+G4s/6boPLhrH4OtC72brfV5h8GnnTLE8/H9NEfk4l6hVClA==", "dev": true, "requires": { "async": "~3.0.1", @@ -10732,12 +10915,12 @@ } }, "karma-jasmine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.1.tgz", + "integrity": "sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==", "dev": true, "requires": { - "jasmine-core": "^3.3" + "jasmine-core": "^3.6.0" } }, "karma-jasmine-html-reporter": { @@ -10747,19 +10930,19 @@ "dev": true }, "karma-junit-reporter": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz", - "integrity": "sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", + "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", "dev": true, "requires": { "path-is-absolute": "^1.0.0", - "xmlbuilder": "8.2.2" + "xmlbuilder": "12.0.0" }, "dependencies": { "xmlbuilder": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", - "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", "dev": true } } @@ -10792,15 +10975,6 @@ } } }, - "karma-remap-coverage": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/karma-remap-coverage/-/karma-remap-coverage-0.1.5.tgz", - "integrity": "sha512-FM5h8eHcHbMMR+2INBUxD+4+wUbkCnobfn5uWprkLyj6Xcm2MRFQOuAJn9h2H13nNso6rk+QoNpHd5xCevlPOw==", - "dev": true, - "requires": { - "remap-istanbul": "^0.10" - } - }, "karma-sourcemap-loader": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz", @@ -11071,11 +11245,31 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, "lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -11085,8 +11279,12 @@ "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, "lodash.memoize": { "version": "3.0.4", @@ -11100,6 +11298,17 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://msazure.pkgs.visualstudio.com/_packaging/AzurePortal/npm/registry/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -11134,6 +11343,15 @@ "streamroller": "^2.2.4" }, "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "flatted": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", @@ -11191,16 +11409,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -11353,9 +11561,9 @@ "dev": true }, "marky": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.1.tgz", - "integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.2.tgz", + "integrity": "sha512-k1dB2HNeaNyORco8ulVEhctyEGkKHb2YWAhDsxeFlW2nROIirsctBYzKwwS3Vza+sKTS1zO4Z+n9/+9WbGLIxQ==", "dev": true }, "matcher": { @@ -11415,177 +11623,45 @@ "longest-streak": "^2.0.0", "mdast-util-to-string": "^2.0.0", "parse-entities": "^2.0.0", - "repeat-string": "^1.0.0", - "zwitch": "^1.0.0" - } - }, - "mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", - "dev": true - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "memfs": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.0.tgz", - "integrity": "sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A==", - "dev": true, - "requires": { - "fs-monkey": "1.0.1" - } - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + } + }, + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memfs": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.0.tgz", + "integrity": "sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A==", + "dev": true, + "requires": { + "fs-monkey": "1.0.1" + } + }, + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, "merge-descriptors": { @@ -11623,6 +11699,17 @@ "requires": { "debug": "^4.0.0", "parse-entities": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } } }, "micromatch": { @@ -12214,15 +12301,6 @@ "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, "normalize-package-data": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", @@ -12318,12 +12396,6 @@ "boolbase": "~1.0.0" } }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true - }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -13039,18 +13111,6 @@ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "optional": true }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13222,55 +13282,15 @@ } }, "plist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", - "integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.2.tgz", + "integrity": "sha512-MSrkwZBdQ6YapHy87/8hDU8MnIcyxBKjeF+McXnr5A9MtffPewTs7G3hlpodT5TacyfIyFTaJEhh3GGcmasTgQ==", "dev": true, "optional": true, "requires": { - "base64-js": "^1.2.3", + "base64-js": "^1.5.1", "xmlbuilder": "^9.0.7", - "xmldom": "0.1.x" - } - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - } + "xmldom": "^0.5.0" } }, "pluralize": { @@ -13690,6 +13710,15 @@ "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", "dev": true }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -13932,9 +13961,9 @@ }, "dependencies": { "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "normalize-package-data": { @@ -14129,27 +14158,6 @@ "picomatch": "^2.2.1" } }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - }, - "dependencies": { - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - } - } - }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -14243,20 +14251,6 @@ "es6-error": "^4.0.1" } }, - "remap-istanbul": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/remap-istanbul/-/remap-istanbul-0.10.1.tgz", - "integrity": "sha512-gsNQXs5kJLhErICSyYhzVZ++C8LBW8dgwr874Y2QvzAUS75zBlD/juZgXs39nbYJ09fZDlX2AVLVJAY2jbFJoQ==", - "dev": true, - "requires": { - "amdefine": "^1.0.0", - "istanbul": "0.4.5", - "minimatch": "^3.0.3", - "plugin-error": "^0.1.2", - "source-map": "^0.6.1", - "through2": "2.0.1" - } - }, "remark": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", @@ -14443,9 +14437,9 @@ } }, "resolve-alpn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", - "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.2.tgz", + "integrity": "sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA==", "dev": true }, "resolve-cwd": { @@ -14557,9 +14551,9 @@ "dev": true }, "rfdc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz", - "integrity": "sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "dev": true }, "rgb2hex": { @@ -14802,23 +14796,6 @@ "semver": "^6.3.0" } }, - "semver-dsl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", - "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", - "dev": true, - "requires": { - "semver": "^5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -15300,120 +15277,58 @@ } }, "socket.io": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz", - "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", + "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", "dev": true, "requires": { - "debug": "~4.1.0", - "engine.io": "~3.5.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.4.0", - "socket.io-parser": "~3.4.0" + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~4.1.0", + "socket.io-adapter": "~2.1.0", + "socket.io-parser": "~4.0.3" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } } } }, "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", + "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==", "dev": true }, - "socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "component-bind": "1.0.0", - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "engine.io-client": "~3.5.0", - "has-binary2": "~1.0.2", - "indexof": "0.0.1", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "socket.io-parser": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", - "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", - "dev": true, - "requires": { - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "isarray": "2.0.1" - } - } - } - }, "socket.io-parser": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", - "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", "dev": true, "requires": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" }, "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true } } }, @@ -15589,6 +15504,17 @@ "http-deceiver": "^1.2.7", "select-hose": "^2.0.0", "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } } }, "spdy-transport": { @@ -15605,6 +15531,15 @@ "wbuf": "^1.7.3" }, "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -15674,7 +15609,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true + "dev": true, + "optional": true }, "sshpk": { "version": "1.16.1", @@ -15895,6 +15831,15 @@ "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", "dev": true }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -15990,15 +15935,6 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -16097,6 +16033,16 @@ "write-file-atomic": "^3.0.3" }, "dependencies": { + "@stylelint/postcss-markdown": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", + "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", + "dev": true, + "requires": { + "remark": "^13.0.0", + "unist-util-find-all-after": "^3.0.2" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -16149,6 +16095,15 @@ "yaml": "^1.10.0" } }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -16423,6 +16378,17 @@ "dev": true, "requires": { "debug": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } } }, "supports-color": { @@ -16563,9 +16529,9 @@ }, "dependencies": { "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -16793,9 +16759,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -16988,12 +16954,6 @@ } } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -17118,12 +17078,6 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, "trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -17305,9 +17259,9 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.22", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz", - "integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==", + "version": "0.7.28", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", + "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", "dev": true }, "uc.micro": { @@ -18052,9 +18006,9 @@ }, "dependencies": { "@sindresorhus/is": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", - "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", "dev": true }, "@szmarczak/http-timer": { @@ -18164,9 +18118,9 @@ "dev": true }, "p-cancelable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", - "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "dev": true }, "responselike": { @@ -18234,9 +18188,9 @@ } }, "serialize-error": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.0.1.tgz", - "integrity": "sha512-r5o60rWFS+8/b49DNAbB+GXZA0SpDpuWE758JxDKgRTga05r3U5lwyksE91dYKDhXSmnu36RALj615E6Aj5pSg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -18638,6 +18592,15 @@ } } }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -19134,12 +19097,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -19281,18 +19238,12 @@ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xmldom": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", - "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz", + "integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==", "dev": true, "optional": true }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -19336,6 +19287,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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", @@ -19380,12 +19337,6 @@ "fd-slicer": "~1.1.0" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, "yesno": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.0.1.tgz", diff --git a/package.json b/package.json index 82d92b1751..7fe22f7d4c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "name": "Microsoft Corporation", "email": "batchexplorer@microsoft.com" }, - "version": "2.9.0", + "version": "2.10.0", "main": "build/client/main.prod.js", "scripts": { "ts": "ts-node --project tsconfig.node.json --files", @@ -97,7 +97,6 @@ "@typescript-eslint/parser": "^4.14.2", "angular2-template-loader": "^0.6.2", "awesome-typescript-loader": "^5.2.1", - "codelyzer": "^5.2.2", "colors": "^1.4.0", "concurrently": "^5.3.0", "copy-webpack-plugin": "^6.0.3", @@ -112,7 +111,8 @@ "eslint-plugin-jsdoc": "^31.6.0", "eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-prettier": "^3.3.1", - "eslint-plugin-unicorn": "^27.0.0", + "eslint-plugin-unicorn": "^31.0.0", + "eslint-plugin-unused-imports": "^1.1.1", "file-loader": "^3.0.1", "fork-ts-checker-webpack-plugin": "^5.1.0", "html-webpack-plugin": "^3.2.0", @@ -120,15 +120,14 @@ "jasmine": "~3.5.0", "jasmine-core": "^3.6.0", "jasmine-spec-reporter": "^4.2.1", - "karma": "^5.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-coverage": "^1.1.2", - "karma-electron": "^6.3.1", - "karma-jasmine": "^2.0.1", + "karma": "^6.3.2", + "karma-chrome-launcher": "^3.1.0", + "karma-coverage": "^2.0.3", + "karma-electron": "^6.3.4", + "karma-jasmine": "^4.0.1", "karma-jasmine-html-reporter": "^1.5.4", - "karma-junit-reporter": "^1.2.0", + "karma-junit-reporter": "^2.0.1", "karma-mocha-reporter": "^2.2.5", - "karma-remap-coverage": "^0.1.5", "karma-sourcemap-loader": "^0.3.8", "karma-webpack": "^4.0.2", "loader-utils": "^1.4.0", @@ -174,6 +173,7 @@ "@angular/platform-browser-dynamic": "^9.1.12", "@angular/platform-server": "^9.1.12", "@angular/router": "^9.1.12", + "@azure/msal-node": "^1.0.1", "@azure/storage-blob": "^10.5.0", "@types/keytar": "^4.4.2", "applicationinsights": "^1.8.5", diff --git a/scripts/publish/publish.ts b/scripts/publish/publish.ts index 4e7d4b9b51..309ee76d5d 100644 --- a/scripts/publish/publish.ts +++ b/scripts/publish/publish.ts @@ -81,7 +81,7 @@ async function confirmVersion(version: string) { ask(`Up program to be version ${version} (From millestone title) [Y/n]`, true, (ok) => { if (ok) { success(`A new release for version ${version} will be prepared`); - resolve(); + resolve(null); } else { reject(new Error("Millestone version wasn't confirmed. Please change millestone title")); } @@ -168,6 +168,9 @@ async function startPublish() { checkGithubToken(); const millestoneId = getMillestoneId(); const millestone = await loadMillestone(millestoneId); + if (!millestone.title && millestone["message"]) { + throw new Error(`Error fetching milestone: ${millestone["message"]}`); + } const version = millestone.title; await confirmVersion(version); const releaseBranch = getPreparationBranchName(version); diff --git a/src/@batch-flask/core/aad/access-token/access-token.model.ts b/src/@batch-flask/core/aad/access-token/access-token.model.ts index e0a6d01c2a..48807a4aea 100644 --- a/src/@batch-flask/core/aad/access-token/access-token.model.ts +++ b/src/@batch-flask/core/aad/access-token/access-token.model.ts @@ -20,6 +20,9 @@ export interface AccessTokenAttributes { ext_expires_in: number; not_before: Date; + + tenantId?: string; + resource?: string; } export class AccessToken { @@ -50,6 +53,9 @@ export class AccessToken { public ext_expires_in: number; public not_before: Date; + public tenantId?: string; + public resource?: string; + constructor(data: AccessTokenAttributes) { this.access_token = data.access_token; this.refresh_token = data.refresh_token; @@ -58,6 +64,8 @@ export class AccessToken { this.expires_on = new Date(data.expires_on); this.ext_expires_in = data.ext_expires_in; this.not_before = new Date(data.not_before); + this.tenantId = data.tenantId; + this.resource = data.resource; } /** diff --git a/src/@batch-flask/ui/buttons/button.component.ts b/src/@batch-flask/ui/buttons/button.component.ts index 84286b938d..025d66423d 100644 --- a/src/@batch-flask/ui/buttons/button.component.ts +++ b/src/@batch-flask/ui/buttons/button.component.ts @@ -107,12 +107,12 @@ export class ButtonComponent extends ClickableComponent { @HostListener("focus") public showTooltip() { - this._tooltip.show(100); + this._tooltip?.show(100); } @HostListener("blur") public hideTooltip() { - this._tooltip.hide(); + this._tooltip?.hide(); } public done() { diff --git a/src/@batch-flask/ui/buttons/clickable/clickable.component.ts b/src/@batch-flask/ui/buttons/clickable/clickable.component.ts index 6ee7a4335f..83269a3fa5 100644 --- a/src/@batch-flask/ui/buttons/clickable/clickable.component.ts +++ b/src/@batch-flask/ui/buttons/clickable/clickable.component.ts @@ -56,7 +56,7 @@ export class ClickableComponent implements OnChanges, OnDestroy { // Aria @Input() @HostBinding("attr.role") public role = "button"; @HostBinding("attr.aria-disabled") public get ariaDisabled() { return this.disabled; } - + public subtitle = ""; private permissionService: PermissionService | null; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a590436773..20b74342c5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,7 +23,7 @@ import { SettingsModule } from "app/components/settings"; import { BatchExplorerErrorHandler } from "app/error-handler"; import { routes } from "./app.routes"; import { - AdalService, + AuthService, AppLocaleService, AppTranslationsLoaderService, RendererConfigurationStore, @@ -72,7 +72,7 @@ const modules = [ { provide: LocaleService, useClass: AppLocaleService }, { provide: USER_CONFIGURATION_STORE, useClass: RendererConfigurationStore }, { provide: ErrorHandler, useClass: BatchExplorerErrorHandler }, - { provide: USER_SERVICE, useExisting: AdalService }, + { provide: USER_SERVICE, useExisting: AuthService }, ], }) export class AppModule { } diff --git a/src/app/components/gallery/application-list/gallery-application-list.html b/src/app/components/gallery/application-list/gallery-application-list.html index ee2a36ce29..6934423a65 100644 --- a/src/app/components/gallery/application-list/gallery-application-list.html +++ b/src/app/components/gallery/application-list/gallery-application-list.html @@ -6,6 +6,7 @@ *ngFor="let application of displayedApplications;trackBy: trackApplication" class="application" [class.active]="application.id === active?.applicationId && application.portfolioId === active?.portfolioId" + [attr.aria-selected]="application.id === active?.applicationId && application.portfolioId === active?.portfolioId" [title]="application.description" (do)="selectApplication(application)"> diff --git a/src/app/components/layout/main-navigation/profile-button/profile-button.component.spec.ts b/src/app/components/layout/main-navigation/profile-button/profile-button.component.spec.ts index 1e05261093..c4600e1cb2 100644 --- a/src/app/components/layout/main-navigation/profile-button/profile-button.component.spec.ts +++ b/src/app/components/layout/main-navigation/profile-button/profile-button.component.spec.ts @@ -15,7 +15,7 @@ import { ContextMenuSeparator, MultiContextMenuItem, } from "@batch-flask/ui"; -import { AdalService, BatchExplorerService } from "app/services"; +import { AuthService, BatchExplorerService } from "app/services"; import { ProgressInfo } from "builder-util-runtime"; import { BehaviorSubject } from "rxjs"; import { click } from "test/utils/helpers"; @@ -32,7 +32,7 @@ describe("ProfileButtonComponent", () => { let fixture: ComponentFixture; let de: DebugElement; let clickableEl: DebugElement; - let adalServiceSpy; + let authServiceSpy; let autoUpdateServiceSpy; let batchExplorerServiceSpy; let contextMenuServiceSpy: ContextMenuServiceMock; @@ -43,7 +43,7 @@ describe("ProfileButtonComponent", () => { checkForUpdatesResponse = Promise.resolve({ updateInfo: { version: "1.2.4" } }); contextMenuServiceSpy = new ContextMenuServiceMock(); notificationServiceSpy = new NotificationServiceMock(); - adalServiceSpy = { + authServiceSpy = { currentUser: new BehaviorSubject(null), }; @@ -58,7 +58,7 @@ describe("ProfileButtonComponent", () => { imports: [MatTooltipModule, RouterTestingModule, I18nTestingModule, MatProgressSpinnerModule], declarations: [ProfileButtonComponent, ClickableComponent, TestComponent], providers: [ - { provide: AdalService, useValue: adalServiceSpy }, + { provide: AuthService, useValue: authServiceSpy }, { provide: AutoUpdateService, useValue: autoUpdateServiceSpy }, { provide: BatchExplorerService, useValue: batchExplorerServiceSpy }, { provide: ElectronShell, useValue: null }, @@ -77,7 +77,7 @@ describe("ProfileButtonComponent", () => { }); it("shows the current user info in tooltip", () => { - adalServiceSpy.currentUser.next({ + authServiceSpy.currentUser.next({ name: "Some Name", unique_name: "some.name@example.com", }); diff --git a/src/app/components/layout/main-navigation/profile-button/profile-button.component.ts b/src/app/components/layout/main-navigation/profile-button/profile-button.component.ts index f4aa3a3def..6e13d05a45 100644 --- a/src/app/components/layout/main-navigation/profile-button/profile-button.component.ts +++ b/src/app/components/layout/main-navigation/profile-button/profile-button.component.ts @@ -14,7 +14,7 @@ import { import { NotificationService } from "@batch-flask/ui/notifications"; import { OS, log } from "@batch-flask/utils"; import { - AdalService, BatchExplorerService, + AuthService, BatchExplorerService, } from "app/services"; import { Constants } from "common"; import * as path from "path"; @@ -39,7 +39,7 @@ export class ProfileButtonComponent implements OnDestroy, OnInit { private _destroy = new Subject(); constructor( - adalService: AdalService, + authService: AuthService, private i18n: I18nService, private localeService: LocaleService, private changeDetector: ChangeDetectorRef, @@ -53,7 +53,7 @@ export class ProfileButtonComponent implements OnDestroy, OnInit { private fs: FileSystemService, private router: Router) { - adalService.currentUser.pipe(takeUntil(this._destroy)).subscribe((user) => { + authService.currentUser.pipe(takeUntil(this._destroy)).subscribe((user) => { if (user) { this.currentUserName = `${user.name} (${user.unique_name})`; } else { diff --git a/src/app/components/pool/graphs/pool-state-graph/pool-state-graph.component.ts b/src/app/components/pool/graphs/pool-state-graph/pool-state-graph.component.ts index 470884d495..c3773c07bf 100644 --- a/src/app/components/pool/graphs/pool-state-graph/pool-state-graph.component.ts +++ b/src/app/components/pool/graphs/pool-state-graph/pool-state-graph.component.ts @@ -66,11 +66,11 @@ export class PoolStateGraphComponent implements OnChanges, OnDestroy { constructor( private changeDetector: ChangeDetectorRef, - private poolNodeCountSerivce: PoolNodeCountService, + private poolNodeCountService: PoolNodeCountService, private contextMenuService: ContextMenuService) { this._updateDataSets(); this._updateOptions(); - this._sub = poolNodeCountSerivce.counts.subscribe((counts) => { + this._sub = poolNodeCountService.counts.subscribe((counts) => { this._counts = counts; this._updateDataSets(); }); @@ -93,7 +93,7 @@ export class PoolStateGraphComponent implements OnChanges, OnDestroy { @HostListener("contextmenu") public showContextMenu() { this.contextMenuService.openMenu(new ContextMenu([ - new ContextMenuItem({ label: "Refresh", click: () => this.poolNodeCountSerivce.refresh().subscribe() }), + new ContextMenuItem({ label: "Refresh", click: () => this.poolNodeCountService.refresh().subscribe() }), ])); } diff --git a/src/app/models/tenant-details.ts b/src/app/models/tenant-details.ts index b9cfd2a167..b87ebe2d92 100644 --- a/src/app/models/tenant-details.ts +++ b/src/app/models/tenant-details.ts @@ -1,8 +1,9 @@ import { Model, Prop, Record } from "@batch-flask/core"; export interface TenantDetailsAttributes { - objectId: string; + id: string; displayName: string; + tenantId: string; } /** @@ -10,10 +11,13 @@ export interface TenantDetailsAttributes { */ @Model() export class TenantDetails extends Record { - @Prop() public objectId: string; @Prop() public displayName: string; - - public get id() { - return this.objectId; - } + @Prop() public countryCode: string; + @Prop() public defaultDomain: string; + @Prop() public domains: string[]; + @Prop() public id: string; + @Prop() public tenantBrandingLogoUrl: string; + @Prop() public tenantCategory: string; + @Prop() public tenantId: string; + @Prop() public tenantType: string; } diff --git a/src/app/models/vm-size.ts b/src/app/models/vm-size.ts index 91c5abf65b..3545aaf813 100644 --- a/src/app/models/vm-size.ts +++ b/src/app/models/vm-size.ts @@ -1,5 +1,4 @@ import { Model, Prop, Record } from "@batch-flask/core"; -import { symlink } from "fs"; import { List } from "immutable"; export interface VmSizeAttributes { diff --git a/src/app/services/adal/adal.service.spec.ts b/src/app/services/aad/auth.service.spec.ts similarity index 77% rename from src/app/services/adal/adal.service.spec.ts rename to src/app/services/aad/auth.service.spec.ts index 68bae4c28d..eaa42d9131 100644 --- a/src/app/services/adal/adal.service.spec.ts +++ b/src/app/services/aad/auth.service.spec.ts @@ -1,5 +1,5 @@ import { AccessToken, ServerError } from "@batch-flask/core"; -import { AdalService } from "app/services/adal"; +import { AuthService } from "app/services/aad"; import { DateTime } from "luxon"; import { BehaviorSubject } from "rxjs"; @@ -15,8 +15,8 @@ const token1 = new AccessToken({ refresh_token: "foorefresh", }); -describe("AdalService spec", () => { - let service: AdalService; +describe("AuthService spec", () => { + let service: AuthService; let aadServiceSpy; let remoteSpy; let batchExplorerSpy; @@ -25,7 +25,7 @@ describe("AdalService spec", () => { beforeEach(() => { aadServiceSpy = { - tenantsIds: new BehaviorSubject([]), + tenants: new BehaviorSubject([]), currentUser: new BehaviorSubject([]), }; remoteSpy = { @@ -42,19 +42,19 @@ describe("AdalService spec", () => { zoneSpy = { run: jasmine.createSpy("zone.run").and.callFake(callback => callback()), }; - service = new AdalService(zoneSpy, batchExplorerSpy, remoteSpy, notificationServiceSpy); + service = new AuthService(zoneSpy, batchExplorerSpy, remoteSpy, notificationServiceSpy); }); afterEach(() => { - aadServiceSpy.tenantsIds.complete(); + aadServiceSpy.tenants.complete(); service.ngOnDestroy(); }); - it("It notify of error if tenants ids fail", () => { + it("raises an error when tenants cannot be fetched", () => { const nextSpy = jasmine.createSpy("next"); const errorSpy = jasmine.createSpy("error"); - service.tenantsIds.subscribe(nextSpy, errorSpy); - aadServiceSpy.tenantsIds.error(new ServerError({ + service.tenants.subscribe(nextSpy, errorSpy); + aadServiceSpy.tenants.error(new ServerError({ status: 300, code: "ERRNOCONN", statusText: "ERRNOCONN", @@ -110,31 +110,35 @@ describe("AdalService spec", () => { expect(tokenB).toEqual(token1); }); - it("it calls again the main process if previous call returned an error", async () => { + it("calls again the main process if previous call returned an error", async () => { remoteSpy.send = jasmine.createSpy("send").and.returnValues( Promise.reject("some-error"), Promise.resolve(token1), ); try { await service.accessTokenDataAsync(tenant1, resource1); - fail("Shouldn't have succeeded"); + fail("First call to accessTokenDataAsync shouldn't have succeeded"); } catch (e) { expect(remoteSpy.send).toHaveBeenCalledTimes(1); expect(e).toEqual("some-error"); } - const token = await service.accessTokenDataAsync(tenant1, resource1); - expect(remoteSpy.send).toHaveBeenCalledTimes(2); - expect(token).toEqual(token1); + try { + const token = await service.accessTokenDataAsync(tenant1, resource1); + expect(remoteSpy.send).toHaveBeenCalledTimes(2); + expect(token).toEqual(token1); + } catch (e) { + fail(`Second call to accessTokenDataAsync should have succeeded [err=${e}]`); + } }); }); - it("updates the tenants ids when updated by the adal service", () => { - let tenantIds; - service.tenantsIds.subscribe(x => tenantIds = x); + it("updates the tenants when updated by the auth service", () => { + let tenants; + service.tenants.subscribe(x => tenants = x); expect(zoneSpy.run).toHaveBeenCalledTimes(1); - aadServiceSpy.tenantsIds.next(["foo-1", "foo-2"]); + aadServiceSpy.tenants.next(["foo-1", "foo-2"]); expect(zoneSpy.run).toHaveBeenCalledTimes(2); - expect(tenantIds).toEqual(["foo-1", "foo-2"]); + expect(tenants).toEqual(["foo-1", "foo-2"]); }); }); diff --git a/src/app/services/adal/adal.service.ts b/src/app/services/aad/auth.service.ts similarity index 87% rename from src/app/services/adal/adal.service.ts rename to src/app/services/aad/auth.service.ts index fa2edf2b43..d160b748a2 100644 --- a/src/app/services/adal/adal.service.ts +++ b/src/app/services/aad/auth.service.ts @@ -3,17 +3,18 @@ import { AccessToken, AccessTokenCache, ServerError } from "@batch-flask/core"; import { ElectronRemote } from "@batch-flask/electron"; import { wrapMainObservable } from "@batch-flask/electron/utils"; import { NotificationService } from "@batch-flask/ui"; +import { TenantDetails } from "app/models"; import { BatchExplorerService } from "app/services/batch-explorer.service"; import { AADResourceName } from "client/azure-environment"; import { AADService } from "client/core/aad"; -import { AADUser } from "client/core/aad/adal/aad-user"; +import { AADUser } from "client/core/aad/auth/aad-user"; import { Constants } from "common"; import { Observable, from, throwError } from "rxjs"; import { catchError, publishReplay, refCount } from "rxjs/operators"; @Injectable({ providedIn: "root" }) -export class AdalService implements OnDestroy { - public tenantsIds: Observable; +export class AuthService implements OnDestroy { + public tenants: Observable; public currentUser: Observable; private _aadService: AADService; @@ -28,7 +29,7 @@ export class AdalService implements OnDestroy { this._aadService = batchExplorer.aadService; this.currentUser = wrapMainObservable(this._aadService.currentUser, zone); - this.tenantsIds = wrapMainObservable(this._aadService.tenantsIds, zone).pipe( + this.tenants = wrapMainObservable(this._aadService.tenants, zone).pipe( catchError((error) => { const serverError = new ServerError(error); this.notificationService.error( @@ -81,14 +82,16 @@ export class AdalService implements OnDestroy { } } - const promise = this.remote.send(Constants.IpcEvent.AAD.accessTokenData, { tenantId, resource }).then((x) => { + const promise = this.remote.send( + Constants.IpcEvent.AAD.accessTokenData, { tenantId, resource }) + .then((x) => { const token = new AccessToken({ ...x }); this.tokenCache.storeToken(tenantId, resource, token); - delete this._waitingPromises[key]; return token; }).catch((e) => { - delete this._waitingPromises[key]; return Promise.reject(e); + }).finally(() => { + delete this._waitingPromises[key]; }); this._waitingPromises[key] = promise; return promise; diff --git a/src/app/services/aad/index.ts b/src/app/services/aad/index.ts new file mode 100644 index 0000000000..2ab33e86ab --- /dev/null +++ b/src/app/services/aad/index.ts @@ -0,0 +1 @@ +export * from "./auth.service"; diff --git a/src/app/services/adal/index.ts b/src/app/services/adal/index.ts deleted file mode 100644 index d6a28a4cc8..0000000000 --- a/src/app/services/adal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./adal.service"; diff --git a/src/app/services/app-insights/app-insights-api.service.ts b/src/app/services/app-insights/app-insights-api.service.ts index 047354c6fa..8ba2f36fd2 100644 --- a/src/app/services/app-insights/app-insights-api.service.ts +++ b/src/app/services/app-insights/app-insights-api.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { AccessToken, HttpInterface, HttpMethod, HttpRequestOptions, RetryableHttpCode, ServerError, } from "@batch-flask/core"; import { ArmBatchAccount } from "app/models"; -import { AdalService } from "app/services/adal"; +import { AuthService } from "app/services/aad"; import { BatchExplorerService } from "app/services/batch-explorer.service"; import { Constants } from "common"; import { Observable, throwError, timer } from "rxjs"; @@ -24,7 +24,7 @@ function mergeOptions(original: HttpRequestOptions, body?: any): HttpRequestOpti export class AppInsightsApiService implements HttpInterface { constructor( private http: HttpClient, - private adal: AdalService, + private auth: AuthService, private accountService: BatchAccountService, private batchExplorer: BatchExplorerService) { } @@ -54,7 +54,7 @@ export class AppInsightsApiService implements HttpInterface { } }), switchMap((account: ArmBatchAccount) => { - return this.adal.accessTokenData(account.subscription.tenantId, "appInsights"); + return this.auth.accessTokenData(account.subscription.tenantId, "appInsights"); }), switchMap((accessToken) => { options = this._setupRequestOptions(options, accessToken); diff --git a/src/app/services/arm-http.service.ts b/src/app/services/arm-http.service.ts index 6ef5491f41..57197b2a13 100644 --- a/src/app/services/arm-http.service.ts +++ b/src/app/services/arm-http.service.ts @@ -3,7 +3,7 @@ import { HttpInterface, HttpMethod, HttpRequestOptions, ServerError } from "@bat import { Observable } from "rxjs"; import { first, share, switchMap, tap } from "rxjs/operators"; import { ArmBatchAccount } from "../models"; -import { AdalService } from "./adal"; +import { AuthService } from "./aad"; import { AzureHttpService } from "./azure-http.service"; import { BatchAccountService } from "./batch-account"; @@ -21,7 +21,7 @@ function mergeOptions(original: HttpRequestOptions, body?: any): HttpRequestOpti */ @Injectable({ providedIn: "root" }) export class ArmHttpService implements HttpInterface { - constructor(private http: AzureHttpService, adal: AdalService, private accountService: BatchAccountService) { + constructor(private http: AzureHttpService, auth: AuthService, private accountService: BatchAccountService) { } public request(method: HttpMethod, uri: string, options: HttpRequestOptions): Observable { diff --git a/src/app/services/arm-location/arm-location.service.spec.ts b/src/app/services/arm-location/arm-location.service.spec.ts index 834685cc24..d45d111a5c 100644 --- a/src/app/services/arm-location/arm-location.service.spec.ts +++ b/src/app/services/arm-location/arm-location.service.spec.ts @@ -2,16 +2,17 @@ import { HttpClientTestingModule, HttpTestingController } from "@angular/common/ import { TestBed } from "@angular/core/testing"; import { AccessToken } from "@batch-flask/core"; import { ArmLocation, ArmSubscription, TenantDetails } from "app/models"; +import { Constants } from "common"; import { List } from "immutable"; import { BehaviorSubject, of } from "rxjs"; -import { AdalService } from "../adal"; +import { AuthService } from "../aad"; import { AzureHttpService } from "../azure-http.service"; import { BatchExplorerService } from "../batch-explorer.service"; import { ArmLocationService } from "./arm-location.service"; const tenantDetails: StringMap = { - "tenant-1": new TenantDetails({ displayName: "Tenant 1", objectId: "tenant-1" }), - "tenant-2": new TenantDetails({ displayName: "Tenant 2", objectId: "tenant-2" }), + "tenant-1": new TenantDetails({ displayName: "Tenant 1" }), + "tenant-2": new TenantDetails({ displayName: "Tenant 2" }), }; const tokens: StringMap = { @@ -65,12 +66,12 @@ const batchLoc2 = new ArmLocation({ describe("ArmLocationService", () => { let service: ArmLocationService; - let adalSpy; + let authSpy; let armProviderServiceSpy; let httpMock: HttpTestingController; beforeEach(() => { - adalSpy = { + authSpy = { tenantsIds: new BehaviorSubject(["tenant-1", "tenant-2"]), accessTokenData: jasmine.createSpy("accessTokenData").and.callFake((id) => { return of(tokens[id]); @@ -95,7 +96,7 @@ describe("ArmLocationService", () => { }, }, }, - { provide: AdalService, useValue: adalSpy }, + { provide: AuthService, useValue: authSpy }, ], }); @@ -113,7 +114,7 @@ describe("ArmLocationService", () => { }); const reqs = httpMock.expectOne( - "https://management.azure.com/subscriptions/sub1/locations?api-version=2016-09-01", + `https://management.azure.com/subscriptions/sub1/locations?api-version=${Constants.ApiVersion.arm}`, ); expect(reqs.request.body).toBe(null); @@ -135,7 +136,7 @@ describe("ArmLocationService", () => { sub1, "Microsoft.Batch", "batchAccounts"); const reqs = httpMock.expectOne( - "https://management.azure.com/subscriptions/sub1/locations?api-version=2016-09-01", + `https://management.azure.com/subscriptions/sub1/locations?api-version=${Constants.ApiVersion.arm}`, ); expect(reqs.request.body).toBe(null); diff --git a/src/app/services/azure-batch/core/batch-http.service.ts b/src/app/services/azure-batch/core/batch-http.service.ts index 9e1e8b0f21..c9a5fff560 100644 --- a/src/app/services/azure-batch/core/batch-http.service.ts +++ b/src/app/services/azure-batch/core/batch-http.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpRequestOptions, HttpService, ServerError } from "@batch-flask/core"; import { UrlUtils } from "@batch-flask/utils"; import { ArmBatchAccount, BatchAccount, LocalBatchAccount } from "app/models"; -import { AdalService } from "app/services/adal"; +import { AuthService } from "app/services/aad"; import { BatchAccountService } from "app/services/batch-account"; import { BatchExplorerService } from "app/services/batch-explorer.service"; import { Constants } from "common"; @@ -23,7 +23,7 @@ export class AzureBatchHttpService extends HttpService { constructor( httpHandler: HttpHandler, - private adal: AdalService, + private auth: AuthService, private accountService: BatchAccountService, private batchExplorer: BatchExplorerService) { super(httpHandler); @@ -72,7 +72,7 @@ export class AzureBatchHttpService extends HttpService { private _setupRequestForArm(account: ArmBatchAccount, options) { const tenantId = account.subscription.tenantId; - return this.adal.accessTokenData(tenantId, "batch").pipe( + return this.auth.accessTokenData(tenantId, "batch").pipe( map((accessToken) => this.addAuthorizationHeader(options, accessToken)), ); } diff --git a/src/app/services/azure-http.service.ts b/src/app/services/azure-http.service.ts index 8fa1b6a0f9..451405cd30 100644 --- a/src/app/services/azure-http.service.ts +++ b/src/app/services/azure-http.service.ts @@ -2,13 +2,12 @@ import { Location } from "@angular/common"; import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { AccessToken, HttpRequestOptions, RetryableHttpCode, ServerError } from "@batch-flask/core"; -import { SanitizedError } from "@batch-flask/utils"; import { ArmSubscription } from "app/models"; import { ArmResourceUtils } from "app/utils"; import { Constants } from "common"; import { Observable, throwError, timer } from "rxjs"; import { catchError, mergeMap, retryWhen, share, switchMap } from "rxjs/operators"; -import { AdalService } from "./adal"; +import { AuthService } from "./aad"; import { BatchExplorerService } from "./batch-explorer.service"; function mergeOptions(original: HttpRequestOptions, body?: any): HttpRequestOptions { @@ -34,7 +33,7 @@ export class InvalidSubscriptionOrTenant extends Error { */ @Injectable({ providedIn: "root" }) export class AzureHttpService { - constructor(private http: HttpClient, private adal: AdalService, private batchExplorer: BatchExplorerService) { + constructor(private http: HttpClient, private auth: AuthService, private batchExplorer: BatchExplorerService) { } public request( @@ -42,7 +41,7 @@ export class AzureHttpService { subscriptionOrTenant: SubscriptionOrTenant, uri: string, options: HttpRequestOptions): Observable { - return this.adal.accessTokenData(this._getTenantId(subscriptionOrTenant, uri)).pipe( + return this.auth.accessTokenData(this._getTenantId(subscriptionOrTenant, uri)).pipe( switchMap((accessToken) => { options = this._setupRequestOptions(uri, options, accessToken); return this.http.request(method, this._computeUrl(uri), options).pipe( diff --git a/src/app/services/index.ts b/src/app/services/index.ts index 4f36eb754a..b88a578fad 100644 --- a/src/app/services/index.ts +++ b/src/app/services/index.ts @@ -28,13 +28,12 @@ export * from "./resource-access"; export * from "./ssh-key.service"; export * from "./subscription"; export * from "./compute"; -export * from "./adal"; +export * from "./aad"; export * from "./python-rpc"; export * from "./storage-account.service"; export * from "./predefined-formula.service"; export * from "./node-connect"; export * from "./themes"; -export * from "./tenant-details.service"; export * from "./network"; export * from "./user-configuration"; export * from "./version"; diff --git a/src/app/services/ms-graph/core/aad-graph-http.service.ts b/src/app/services/ms-graph/core/aad-graph-http.service.ts index d2d7a44056..5e298cef40 100644 --- a/src/app/services/ms-graph/core/aad-graph-http.service.ts +++ b/src/app/services/ms-graph/core/aad-graph-http.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpRequestOptions, HttpService, ServerError } from "@batch-flask/core"; import { UrlUtils } from "@batch-flask/utils"; import { ArmBatchAccount } from "app/models"; -import { AdalService } from "app/services/adal"; +import { AuthService } from "app/services/aad"; import { BatchAccountService } from "app/services/batch-account"; import { BatchExplorerService } from "app/services/batch-explorer.service"; import { Constants } from "common"; @@ -19,7 +19,7 @@ export class AADGraphHttpService extends HttpService { constructor( httpHandler: HttpHandler, - private adal: AdalService, + private auth: AuthService, private accountService: BatchAccountService, private batchExplorer: BatchExplorerService) { super(httpHandler); @@ -39,7 +39,7 @@ export class AADGraphHttpService extends HttpService { }), flatMap((account: ArmBatchAccount) => { const tenantId = account.subscription.tenantId; - return this.adal.accessTokenData(tenantId, "aadGraph").pipe( + return this.auth.accessTokenData(tenantId, "aadGraph").pipe( flatMap((accessToken) => { options = this.addAuthorizationHeader(options, accessToken); options = this._addApiVersion(options); diff --git a/src/app/services/ms-graph/core/ms-graph-http.service.ts b/src/app/services/ms-graph/core/ms-graph-http.service.ts index b6164dea8c..755d4fe8bb 100644 --- a/src/app/services/ms-graph/core/ms-graph-http.service.ts +++ b/src/app/services/ms-graph/core/ms-graph-http.service.ts @@ -2,7 +2,7 @@ import { HttpHandler } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { HttpService, ServerError } from "@batch-flask/core"; import { ArmBatchAccount } from "app/models"; -import { AdalService } from "app/services/adal"; +import { AuthService } from "app/services/aad"; import { BatchAccountService } from "app/services/batch-account"; import { BatchExplorerService } from "app/services/batch-explorer.service"; import { Observable, throwError } from "rxjs"; @@ -19,7 +19,7 @@ export class MsGraphHttpService extends HttpService { constructor( httpHandler: HttpHandler, - private adal: AdalService, + private auth: AuthService, private accountService: BatchAccountService, private batchExplorer: BatchExplorerService) { @@ -39,7 +39,7 @@ export class MsGraphHttpService extends HttpService { } }), flatMap((account: ArmBatchAccount) => { - return this.adal.accessTokenData(account.subscription.tenantId, "msGraph"); + return this.auth.accessTokenData(account.subscription.tenantId, "msGraph"); }), flatMap((accessToken) => { options = this.addAuthorizationHeader(options, accessToken); diff --git a/src/app/services/python-rpc/python-rpc.service.ts b/src/app/services/python-rpc/python-rpc.service.ts index 0757a576ab..fc59fe7751 100644 --- a/src/app/services/python-rpc/python-rpc.service.ts +++ b/src/app/services/python-rpc/python-rpc.service.ts @@ -7,7 +7,7 @@ import { BatchExplorerService } from "app/services/batch-explorer.service"; import { PythonRpcServerProcess } from "client/python-process"; import { AsyncSubject, BehaviorSubject, Observable, Subject, forkJoin, of } from "rxjs"; import { catchError, delay, first, retryWhen, share, switchMap, take, tap } from "rxjs/operators"; -import { AdalService } from "../adal"; +import { AuthService } from "../aad"; import { BatchAccountService } from "../batch-account"; @Injectable({ providedIn: "root" }) @@ -22,7 +22,7 @@ export class PythonRpcService { constructor( private accountService: BatchAccountService, - private adalService: AdalService, + private authService: AuthService, private _zone: NgZone, private batchExplorer: BatchExplorerService, ) { @@ -144,8 +144,8 @@ export class PythonRpcService { } }), switchMap((account: ArmBatchAccount) => { - const batchToken = this.adalService.accessTokenFor(account.subscription.tenantId, "batch"); - const armToken = this.adalService.accessTokenFor(account.subscription.tenantId, "arm"); + const batchToken = this.authService.accessTokenFor(account.subscription.tenantId, "batch"); + const armToken = this.authService.accessTokenFor(account.subscription.tenantId, "arm"); return forkJoin(batchToken, armToken).pipe( first(), switchMap(([batchToken, armToken]) => { diff --git a/src/app/services/subscription/subscription.service.spec.ts b/src/app/services/subscription/subscription.service.spec.ts index a392ef2e0b..2a72e1821d 100644 --- a/src/app/services/subscription/subscription.service.spec.ts +++ b/src/app/services/subscription/subscription.service.spec.ts @@ -3,15 +3,16 @@ import { TestBed } from "@angular/core/testing"; import { AccessToken } from "@batch-flask/core"; import { MockUserConfigurationService } from "@batch-flask/core/testing"; import { ArmSubscription, TenantDetails } from "app/models"; +import { Constants } from "common"; import { BehaviorSubject, of } from "rxjs"; -import { AdalService } from "../adal"; +import { AuthService } from "../aad"; import { AzureHttpService } from "../azure-http.service"; import { BatchExplorerService } from "../batch-explorer.service"; import { SubscriptionService } from "./subscription.service"; const tenantDetails: StringMap = { - "tenant-1": new TenantDetails({ displayName: "Tenant 1", objectId: "tenant-1" }), - "tenant-2": new TenantDetails({ displayName: "Tenant 2", objectId: "tenant-2" }), + "tenant-1": new TenantDetails({ tenantId: "tenant-1", displayName: "Tenant 1" }), + "tenant-2": new TenantDetails({ tenantId: "tenant-2", displayName: "Tenant 2" }), }; const tokens: StringMap = { @@ -55,26 +56,19 @@ const sub3 = new ArmSubscription({ describe("SubscriptionService", () => { let service: SubscriptionService; - let tenantDetailsServiceSpy; - let adalSpy; + let authSpy; let settingsServiceSpy: MockUserConfigurationService; let httpMock: HttpTestingController; let subscriptions: ArmSubscription[] = []; beforeEach(() => { - adalSpy = { - tenantsIds: new BehaviorSubject(["tenant-1", "tenant-2"]), + authSpy = { + tenants: new BehaviorSubject(Object.values(tenantDetails)), accessTokenData: jasmine.createSpy("accessTokenData").and.callFake((id) => { return of(tokens[id]); }), }; - tenantDetailsServiceSpy = { - get: jasmine.createSpy("tenantDetailsService.get").and.callFake((id) => { - return of(tenantDetails[id]); - }), - }; - TestBed.configureTestingModule({ imports: [ HttpClientTestingModule, @@ -88,7 +82,7 @@ describe("SubscriptionService", () => { }, }, }, - { provide: AdalService, useValue: adalSpy }, + { provide: AuthService, useValue: authSpy }, ], }); @@ -96,7 +90,10 @@ describe("SubscriptionService", () => { settingsServiceSpy = new MockUserConfigurationService({}); service = new SubscriptionService( - tenantDetailsServiceSpy, TestBed.get(AzureHttpService), TestBed.get(AdalService), settingsServiceSpy); + TestBed.get(AzureHttpService), + TestBed.get(AuthService), + settingsServiceSpy + ); service.subscriptions.subscribe(x => subscriptions = x.toJS()); }); @@ -112,12 +109,8 @@ describe("SubscriptionService", () => { done(); }); - expect(tenantDetailsServiceSpy.get).toHaveBeenCalledTimes(2); - expect(tenantDetailsServiceSpy.get).toHaveBeenCalledWith("tenant-1"); - expect(tenantDetailsServiceSpy.get).toHaveBeenCalledWith("tenant-2"); - const reqs = httpMock.match({ - url: "https://management.azure.com/subscriptions?api-version=2016-09-01", + url: `https://management.azure.com/subscriptions?api-version=${Constants.ApiVersion.arm}`, method: "GET", }); expect(reqs.length).toEqual(2); @@ -131,11 +124,11 @@ describe("SubscriptionService", () => { }); reqs[1].flush({ value: [sub1Res], - nextLink: "https://management.azure.com/subscriptions?api-version=2016-09-01&token=next-foo", + nextLink: `https://management.azure.com/subscriptions?api-version=${Constants.ApiVersion.arm}&token=next-foo`, }); const nextReq = httpMock.expectOne({ - url: "https://management.azure.com/subscriptions?api-version=2016-09-01&token=next-foo", + url: `https://management.azure.com/subscriptions?api-version=${Constants.ApiVersion.arm}&token=next-foo`, method: "GET", }); expect(nextReq.request.body).toBe(null); diff --git a/src/app/services/subscription/subscription.service.ts b/src/app/services/subscription/subscription.service.ts index 41b882bee7..e9f92c8d7d 100644 --- a/src/app/services/subscription/subscription.service.ts +++ b/src/app/services/subscription/subscription.service.ts @@ -11,10 +11,9 @@ import { distinctUntilChanged, expand, filter, first, flatMap, map, publishReplay, reduce, refCount, shareReplay, switchMap, takeUntil, } from "rxjs/operators"; -import { AdalService } from "../adal"; +import { AuthService } from "../aad"; import { AzureHttpService } from "../azure-http.service"; import { ArmListResponse } from "../core"; -import { TenantDetailsService } from "../tenant-details.service"; @Injectable({ providedIn: "root" }) export class SubscriptionService implements OnDestroy { @@ -26,9 +25,8 @@ export class SubscriptionService implements OnDestroy { private _destroy = new Subject(); constructor( - private tenantDetailsService: TenantDetailsService, private azure: AzureHttpService, - private adal: AdalService, + private auth: AuthService, private settingsService: UserConfigurationService) { const ignoredPatterns = this.settingsService.watch("subscriptions").pipe( @@ -62,12 +60,12 @@ export class SubscriptionService implements OnDestroy { } public load(): Observable { - const obs = this.adal.tenantsIds.pipe( - filter(ids => ids.length > 0), + const obs = this.auth.tenants.pipe( + filter(tenants => tenants.length > 0), first(), - switchMap((tenantIds) => { - return forkJoin(tenantIds.map(tenantId => this._loadSubscriptionsForTenant(tenantId))); - }), + switchMap((tenants: TenantDetails[]) => forkJoin( + tenants.map(tenant => this._loadSubscriptionsForTenant(tenant))) + ), publishReplay(1), refCount(), ); @@ -121,28 +119,30 @@ export class SubscriptionService implements OnDestroy { ); } - private _loadSubscriptionsForTenant(tenantId: string): Observable { - return this.tenantDetailsService.get(tenantId).pipe( - switchMap((tenantDetails) => { - return this.azure.get>(tenantId, "subscriptions").pipe( - expand((response) => { - if (response.nextLink) { - return this.azure.get(tenantId, response.nextLink); - } else { - return empty(); - } - }), - reduce((subs, response: ArmListResponse) => { - const newSubs = response.value.map(x => this._createSubscription(tenantDetails, x)); - return [...subs, ...newSubs]; - }, []), - ); + private _loadSubscriptionsForTenant(tenant: TenantDetails): Observable { + return this.azure.get>(tenant.tenantId, "subscriptions").pipe( + expand((response) => { + if (response.nextLink) { + return this.azure.get(tenant.tenantId, response.nextLink); + } else { + return empty(); + } }), + reduce((subs, response: ArmListResponse) => { + const newSubs = response.value.map( + sub => this._createSubscription(tenant, sub) + ); + return [...subs, ...newSubs]; + }, []) ); } private _createSubscription(tenant: TenantDetails, data: any): ArmSubscription { - return new ArmSubscription({ ...data, tenant, tenantId: tenant.id }); + return new ArmSubscription({ + ...data, + tenant, + tenantId: tenant.tenantId + }); } private _cacheSubscriptions() { diff --git a/src/app/services/tenant-details.service.ts b/src/app/services/tenant-details.service.ts deleted file mode 100644 index 60a14ac9d8..0000000000 --- a/src/app/services/tenant-details.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http"; -import { Injectable } from "@angular/core"; -import { TenantDetails, TenantDetailsAttributes } from "app/models"; -import { Constants } from "common"; -import { Observable } from "rxjs"; -import { flatMap, map, share } from "rxjs/operators"; -import { AdalService } from "./adal"; -import { BatchExplorerService } from "./batch-explorer.service"; - -@Injectable({providedIn: "root"}) -export class TenantDetailsService { - public get serviceUrl() { - return this.batchExplorer.azureEnvironment.aadGraph; - } - - constructor(private adal: AdalService, private http: HttpClient, private batchExplorer: BatchExplorerService) { - - } - - public get(tenantId: string): Observable { - return this.adal.accessTokenData(tenantId, "aadGraph").pipe( - flatMap((accessToken) => { - const options = { - headers: new HttpHeaders({ - Authorization: `${accessToken.token_type} ${accessToken.access_token}`, - }), - params: new HttpParams({ - fromObject: { - "api-version": Constants.ApiVersion.aadGraph, - }, - }), - }; - const url = `${this.serviceUrl}${tenantId}/tenantDetails`; - return this.http.get<{ value: TenantDetailsAttributes[] }>(url, options); - }), - map((response) => { - const value = response.value[0]; - return value && new TenantDetails(value); - }), - share(), - ); - } -} diff --git a/src/client/core/aad/adal-constants.ts b/src/client/core/aad/aad-constants.ts similarity index 84% rename from src/client/core/aad/adal-constants.ts rename to src/client/core/aad/aad-constants.ts index 9069bc1f80..22be2bd00f 100644 --- a/src/client/core/aad/adal-constants.ts +++ b/src/client/core/aad/aad-constants.ts @@ -18,6 +18,7 @@ export interface TokenUrlParams { grant_type: string; } +const LOGOUT_PATH = "oauth2/logout"; export interface LogoutParams { post_logout_redirect_uri?: string; } @@ -28,7 +29,11 @@ export function authorizeUrl(aadUrl: string, tenant: string, params: AuthorizeUr } export function logoutUrl(aadUrl: string, tenant: string) { - return `${aadUrl}${tenant}/oauth2/logout`; + return `${aadUrl}${tenant}/${LOGOUT_PATH}`; +} + +export function isLogoutURL(url: string) { + return url.endsWith(LOGOUT_PATH); } export function objectToParams(object): string { diff --git a/src/client/core/aad/access-token/access-token.service.ts b/src/client/core/aad/access-token/access-token.service.ts index 75562a2bdc..6e0618892c 100644 --- a/src/client/core/aad/access-token/access-token.service.ts +++ b/src/client/core/aad/access-token/access-token.service.ts @@ -4,7 +4,7 @@ import { AADResourceName } from "client/azure-environment"; import { RequestInit, fetch } from "client/core/fetch"; import { BatchExplorerProperties } from "client/core/properties"; import { AADConfig } from "../aad-config"; -import { objectToParams } from "../adal-constants"; +import { objectToParams } from "../aad-constants"; const contentType = "application/x-www-form-urlencoded"; diff --git a/src/client/core/aad/adal/aad.service.spec.ts b/src/client/core/aad/adal/aad.service.spec.ts deleted file mode 100644 index c9c1d19862..0000000000 --- a/src/client/core/aad/adal/aad.service.spec.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { AccessToken, InMemoryDataStore } from "@batch-flask/core"; -import { AzureChina, AzurePublic } from "client/azure-environment"; -import { Constants } from "common"; -import { DateTime } from "luxon"; -import * as proxyquire from "proxyquire"; -import { MockBrowserWindow, MockSplashScreen } from "test/utils/mocks/windows"; -import { AADUser } from "./aad-user"; -import { AADService } from "./aad.service"; - -const mock = proxyquire.noCallThru(); - -const tenant1 = "tenant1"; -const resource1 = "batch"; - -const sampleUser: AADUser = { - aud: "94ef904d-c21a-4972-9244-b4d6a12b8e13", - iss: "https://sts.windows.net/72f788bf-86f1-41af-21ab-2d7cd011db47/", - iat: 1483372574, - nbf: 1483372574, - exp: 1483376474, - amr: ["pwd", "mfa"], - family_name: "Smith", - given_name: "Frank", - ipaddr: "198.217.117.26", - name: "Frank Smith", - nonce: "be4e7843-305e-42ab-988d-7ee109989d70", - oid: "8a0of62c-3629-4619-abd4-8c2257a282be", - platf: "5", - sub: "0WzjD2jhHJVb-3h2PbwUDCJOIPPIJmQQYE832uFqiII", - tid: "72f988bf-86f1-41af-91ab-2d7cd011db47", - unique_name: "frank.smith@example.com", - upn: "frank.smith@example.com", - ver: "1.0", -}; - -describe("AADService", () => { - let service: AADService; - let appSpy; - let localStorage: InMemoryDataStore; - let ipcMainMock; - let propertiesSpy; - let telemetryManagerSpy; - let dialogSpy; - - beforeEach(() => { - localStorage = new InMemoryDataStore(); - appSpy = { - mainWindow: new MockBrowserWindow(), - splashScreen: new MockSplashScreen(), - }; - - ipcMainMock = { - on: () => null, - }; - - propertiesSpy = { - azureEnvironment: AzurePublic, - }; - telemetryManagerSpy = { - enableTelemetry: jasmine.createSpy("enableTelemetry"), - disableTelemetry: jasmine.createSpy("disableTelemetry"), - }; - - dialogSpy = { - showMessageBox: jasmine.createSpy("showMessageBox").and.returnValue(0), - }; - - const mockAADService = mock("./aad.service", { - electron: { - dialog: dialogSpy, - }, - }); - - service = new mockAADService.AADService( - appSpy, localStorage, propertiesSpy, telemetryManagerSpy, localStorage as any, ipcMainMock); - service.init(); - }); - - it("when there is no item in the localstorage it should not set the id_token", () => { - localStorage.removeItem(Constants.localStorageKey.currentUser); - const tmpService = new AADService( - appSpy, localStorage, propertiesSpy, telemetryManagerSpy, localStorage as any, ipcMainMock); - tmpService.init(); - let user: AADUser | null = null; - tmpService.currentUser.subscribe(x => user = x); - expect(user).toBeNull(); - }); - - it("when localstorage has currentUser it should load it", async (done) => { - await localStorage.setItem(Constants.localStorageKey.currentUser, JSON.stringify(sampleUser)); - const tmpService = new AADService( - appSpy, localStorage, propertiesSpy, telemetryManagerSpy, localStorage as any, ipcMainMock); - await tmpService.init(); - let user: AADUser | null = null; - tmpService.currentUser.subscribe(x => user = x); - expect(user).not.toBeNull(); - expect(user!.upn).toEqual("frank.smith@example.com"); - done(); - }); - - describe("accessTokenData", () => { - let authorizeSpy: jasmine.Spy; - let refreshSpy: jasmine.Spy; - let redeemSpy: jasmine.Spy; - let decodeSpy: jasmine.Spy; - let refreshedToken; - let newToken; - let token: AccessToken; - - beforeEach(() => { - refreshedToken = new AccessToken({ - access_token: "refreshedToken", expires_on: DateTime.local().plus({ hours: 1 }).toJSDate(), - } as any); - newToken = new AccessToken({ - access_token: "newToken", expires_on: DateTime.local().plus({ hours: 1 }), - } as any); - const authorizeResult = { - id_token: "someidtoken", - code: "somecode", - }; - - refreshSpy = jasmine.createSpy("refreshSpy").and.returnValue(Promise.resolve(refreshedToken)); - redeemSpy = jasmine.createSpy("redeemSpy").and.returnValue(Promise.resolve(newToken)); - authorizeSpy = jasmine.createSpy("authorizeSpy").and.returnValue(Promise.resolve(authorizeResult)); - decodeSpy = jasmine.createSpy("decodeSpy").and.returnValue(sampleUser); - - (service as any)._accessTokenService.refresh = refreshSpy; - (service as any)._accessTokenService.redeem = redeemSpy; - (service as any).userAuthorization.authorizeTrySilentFirst = authorizeSpy; - (service as any)._userDecoder.decode = decodeSpy; - }); - - it("should use the cached token if not expired", async () => { - (service as any)._tokenCache.storeToken(tenant1, resource1, new AccessToken({ - access_token: "initialtoken", - expires_on: DateTime.local().plus({ hours: 1 }), - } as any)); - token = await service.accessTokenData(tenant1, resource1); - expect(token).not.toBeNull(); - expect(token.access_token).toEqual("initialtoken"); - }); - - it("should reload a new token if the token is expiring before the safe margin", async () => { - (service as any)._tokenCache.storeToken(tenant1, resource1, new AccessToken({ - access_token: "initialtoken", - expires_on: DateTime.local().plus({ minutes: 1 }).toJSDate(), - refresh_token: "somerefreshtoken", - } as any)); - token = await service.accessTokenData(tenant1, resource1); - expect(redeemSpy).not.toHaveBeenCalled(); - expect(refreshSpy).toHaveBeenCalledTimes(1); - expect(refreshSpy).toHaveBeenCalledWith(resource1, tenant1, "somerefreshtoken"); - - expect(token).not.toBeNull(); - expect(token.access_token).toEqual("refreshedToken"); - }); - - it("should load a new token if getting a token for another resource", async (done) => { - (service as any)._tokenCache.storeToken(tenant1, resource1, new AccessToken({ - access_token: "initialtoken", - expires_on: DateTime.local().plus({ hours: 1 }), - } as any)); - token = await service.accessTokenData(tenant1, "appInsights"); - expect(redeemSpy).toHaveBeenCalled(); - expect(redeemSpy).toHaveBeenCalledWith("appInsights", tenant1, "somecode"); - expect(refreshSpy).not.toHaveBeenCalled(); - - expect(token).not.toBeNull(); - expect(token.access_token).toEqual("newToken"); - done(); - }); - - it("should load a new token if getting a token for another tenant", async (done) => { - (service as any)._tokenCache.storeToken(tenant1, resource1, new AccessToken({ - access_token: "initialtoken", - expires_on: DateTime.local().plus({ hours: 1 }), - } as any)); - token = await service.accessTokenData("tenant-2", resource1); - expect(redeemSpy).toHaveBeenCalled(); - expect(redeemSpy).toHaveBeenCalledWith(resource1, "tenant-2", "somecode"); - expect(refreshSpy).not.toHaveBeenCalled(); - - expect(token).not.toBeNull(); - expect(token.access_token).toEqual("newToken"); - done(); - }); - - describe("when there is no token cached", () => { - beforeEach(async (done) => { - token = await service.accessTokenData(tenant1, resource1); - done(); - }); - - it("should authorize the user", () => { - expect(authorizeSpy).toHaveBeenCalledTimes(1); - expect(decodeSpy).toHaveBeenCalledTimes(1); - expect(decodeSpy).toHaveBeenCalledWith("someidtoken"); - }); - - it("should save the user inside localStorage", async (done) => { - const data = await localStorage.getItem(Constants.localStorageKey.currentUser); - expect(data).toEqual(JSON.stringify(sampleUser)); - done(); - }); - - it("should redeem a new token", () => { - expect(refreshSpy).not.toHaveBeenCalled(); - expect(redeemSpy).toHaveBeenCalledTimes(1); - expect(redeemSpy).toHaveBeenCalledWith(resource1, tenant1, "somecode"); - expect(token.access_token).toEqual("newToken"); - }); - }); - }); - - describe("Login", () => { - beforeEach(() => { - const newToken = new AccessToken({ - access_token: "newToken", expires_on: DateTime.local().plus({ hours: 1 }), - } as any); - spyOn(service, "accessTokenData").and.returnValue(new Promise((resolve) => resolve(newToken))); - }); - - it("login to public cloud", async () => { - await service.login().done; - expect(dialogSpy.showMessageBox).not.toHaveBeenCalled(); - }); - - it("login to national cloud", async () => { - propertiesSpy.azureEnvironment = AzureChina; - await service.login().done; - expect(dialogSpy.showMessageBox).toHaveBeenCalledTimes(1); - expect(dialogSpy.showMessageBox).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/client/core/aad/auth-provider.spec.ts b/src/client/core/aad/auth-provider.spec.ts new file mode 100644 index 0000000000..cdf9a1a312 --- /dev/null +++ b/src/client/core/aad/auth-provider.spec.ts @@ -0,0 +1,88 @@ +import AuthProvider from "./auth-provider"; + +describe("AuthProvider", () => { + let authProvider: AuthProvider; + const authCodeCallbackSpy = jasmine.createSpy("authCodeCallback") + .and.returnValue("some-code"); + const appSpy: any = { + properties: { + azureEnvironment: { + aadUrl: "my-URL", + arm: "my-arm", + batch: "my-batch" + } + } + }; + const config: any = { + tenant: "common", + redirectUri: "my-redirect-uri", + clientId: "my-client-id" + }; + + beforeEach(() => { + authProvider = new AuthProvider(appSpy, config); + }); + + it("authenticates interactively first, then silently", async () => { + const call = () => authProvider.getToken({ + resourceURI: "resourceURI1", tenantId: "tenant1", + authCodeCallback: authCodeCallbackSpy + }); + const clientSpy = makeClientApplicationSpy(); + spyOn(authProvider, "_getClient").and.returnValue(clientSpy); + + returnToken(clientSpy.acquireTokenByCode, "interactive-token-1"); + returnToken(clientSpy.acquireTokenSilent, "silent-token-1"); + + const result1 = await call(); + expect(authCodeCallbackSpy).toHaveBeenCalled(); + expect(clientSpy.getAuthCodeUrl).toHaveBeenCalled(); + expect(clientSpy.acquireTokenByCode).toHaveBeenCalled(); + expect(clientSpy.acquireTokenSilent).not.toHaveBeenCalled(); + expect(result1.accessToken).toEqual("interactive-token-1"); + + clientSpy.getAuthCodeUrl.calls.reset(); + clientSpy.acquireTokenByCode.calls.reset(); + authCodeCallbackSpy.calls.reset(); + + const result2 = await call(); + expect(authCodeCallbackSpy).not.toHaveBeenCalled(); + expect(clientSpy.getAuthCodeUrl).not.toHaveBeenCalled(); + expect(clientSpy.acquireTokenByCode).not.toHaveBeenCalled(); + expect(clientSpy.acquireTokenSilent).toHaveBeenCalled(); + expect(result2.accessToken).toEqual("silent-token-1"); + }); + + it("should return a per-tenant access token", async () => { + spyOn(authProvider, "_getClient").and.callFake(tenantId => { + const spy = makeClientApplicationSpy(); + returnToken(spy.acquireTokenByCode, `${tenantId}-token`); + return spy; + }); + + const result1 = await authProvider.getToken({ + resourceURI: "resourceURI1", + tenantId: "tenant1", + authCodeCallback: authCodeCallbackSpy + }); + + const result2 = await authProvider.getToken({ + resourceURI: "resourceURI1", + tenantId: "tenant2", + authCodeCallback: authCodeCallbackSpy + }); + + expect(result1.accessToken).toEqual("tenant1-token"); + expect(result2.accessToken).toEqual("tenant2-token"); + }); +}); + +const makeClientApplicationSpy = () => jasmine.createSpyObj( + "ClientApplication", [ + "acquireTokenSilent", "getAuthCodeUrl", "acquireTokenByCode" + ]); + +const returnToken = (spy, token) => spy.and.returnValue({ + accessToken: token, + account: "some-account" +}); diff --git a/src/client/core/aad/auth-provider.ts b/src/client/core/aad/auth-provider.ts new file mode 100644 index 0000000000..cdf694670c --- /dev/null +++ b/src/client/core/aad/auth-provider.ts @@ -0,0 +1,124 @@ +import { + AccountInfo, + AuthenticationResult, + ClientApplication, + PublicClientApplication +} from "@azure/msal-node"; +import { BatchExplorerApplication } from ".."; +import { AADConfig } from "./aad-config"; + +const MSAL_SCOPES = ["user_impersonation"]; + +export type AuthorizationResult = AuthenticationResult; + +/** + * Provides authentication services + */ +export default class AuthProvider { + private _clients: StringMap = {}; + private _accounts: StringMap = {}; + + constructor( + protected app: BatchExplorerApplication, + protected config: AADConfig + ) {} + + /** + * Retrieves an access token + * + * Will attempt to retrieve the token silently if an account exists in the + * cache, but will fall back to interactive access token retrieval. + * + * @param authCodeCallback Handles interactive authentication code retrieval + */ + public async getToken(options: { + resourceURI: string, + tenantId?: string, + authCodeCallback: (url: string, silent?: boolean) => Promise + }): Promise { + const { resourceURI, tenantId = "common", authCodeCallback } = options; + + /** + * KLUDGE: msal.js does not handle well access tokens across multiple + * tenants within the same cache. It lets you specify a different + * authority per request but it returns the same access token. + * + * Until this is resolved, we use one client application per tenant. + */ + const client = this._getClient(tenantId); + + const authRequest = this._authRequest(resourceURI, tenantId); + if (this._accounts[tenantId]) { + const result = await client.acquireTokenSilent({ + ...authRequest, account: this._accounts[tenantId] + }); + return result; + } else { + let url, code; + + try { + // Attempt to get authorization code silently + url = await client.getAuthCodeUrl( + { ...authRequest, prompt: "none" } + ); + code = await authCodeCallback(url, true); + } catch (e) { + url = await client.getAuthCodeUrl(authRequest); + code = await authCodeCallback(url); + } + + const result: AuthorizationResult = + await client.acquireTokenByCode({ ...authRequest, code }); + if (result) { + this._accounts[tenantId] = result.account; + } + return result; + } + } + + public logout(): void { + this._removeAccount(); + } + + protected _getClient(tenantId: string): ClientApplication { + if (tenantId in this._clients) { + return this._clients[tenantId]; + } + return this._clients[tenantId] = new PublicClientApplication({ + auth: { + clientId: this.config.clientId, + authority: + `${this.app.properties.azureEnvironment.aadUrl}${tenantId}/` + } + }); + } + + private async _removeAccount(): Promise { + for (const tenantId in this._clients) { + if (this._accounts[tenantId]) { + const cache = this._clients[tenantId].getTokenCache(); + cache.removeAccount(this._accounts[tenantId]); + } + delete this._accounts[tenantId]; + } + } + + private _getScopes(resourceURI: string): string[] { + switch (resourceURI) { + case this.app.properties.azureEnvironment.arm: + case this.app.properties.azureEnvironment.batch: + return MSAL_SCOPES.map(s => `${resourceURI}/${s}`); + default: + return MSAL_SCOPES.map(s => `${resourceURI}${s}`); + } + } + + private _authRequest(resourceURI: string, tenantId?: string) { + return { + scopes: this._getScopes(resourceURI), + redirectUri: this.config.redirectUri, + authority: + `${this.app.properties.azureEnvironment.aadUrl}${tenantId}/` + }; + } +} diff --git a/src/client/core/aad/adal/aad-user.ts b/src/client/core/aad/auth/aad-user.ts similarity index 100% rename from src/client/core/aad/adal/aad-user.ts rename to src/client/core/aad/auth/aad-user.ts diff --git a/src/client/core/aad/auth/aad.service.spec.ts b/src/client/core/aad/auth/aad.service.spec.ts new file mode 100644 index 0000000000..fb4b4e239a --- /dev/null +++ b/src/client/core/aad/auth/aad.service.spec.ts @@ -0,0 +1,120 @@ +import { AccessToken, InMemoryDataStore } from "@batch-flask/core"; +import { AzureChina, AzurePublic } from "client/azure-environment"; +import { Constants } from "common"; +import { DateTime } from "luxon"; +import * as proxyquire from "proxyquire"; +import { MockBrowserWindow, MockSplashScreen } from "test/utils/mocks/windows"; +import { AADUser } from "./aad-user"; +import { AADService } from "./aad.service"; + +const mock = proxyquire.noCallThru(); + +const sampleUser: AADUser = { + aud: "94ef904d-c21a-4972-9244-b4d6a12b8e13", + iss: "https://sts.windows.net/72f788bf-86f1-41af-21ab-2d7cd011db47/", + iat: 1483372574, + nbf: 1483372574, + exp: 1483376474, + amr: ["pwd", "mfa"], + family_name: "Smith", + given_name: "Frank", + ipaddr: "198.217.117.26", + name: "Frank Smith", + nonce: "be4e7843-305e-42ab-988d-7ee109989d70", + oid: "8a0of62c-3629-4619-abd4-8c2257a282be", + platf: "5", + sub: "0WzjD2jhHJVb-3h2PbwUDCJOIPPIJmQQYE832uFqiII", + tid: "72f988bf-86f1-41af-91ab-2d7cd011db47", + unique_name: "frank.smith@example.com", + upn: "frank.smith@example.com", + ver: "1.0", +}; + +describe("AADService", () => { + let service: AADService; + let appSpy; + let localStorage: InMemoryDataStore; + let ipcMainMock; + let propertiesSpy; + let telemetryManagerSpy; + let dialogSpy; + + beforeEach(() => { + localStorage = new InMemoryDataStore(); + appSpy = { + mainWindow: new MockBrowserWindow(), + splashScreen: new MockSplashScreen(), + properties: { azureEnvironment: "public" } + }; + + ipcMainMock = { + on: () => null, + }; + + propertiesSpy = { + azureEnvironment: AzurePublic, + }; + telemetryManagerSpy = { + enableTelemetry: jasmine.createSpy("enableTelemetry"), + disableTelemetry: jasmine.createSpy("disableTelemetry"), + }; + + dialogSpy = { + showMessageBox: jasmine.createSpy("showMessageBox").and.returnValue(0), + }; + + const mockAADService = mock("./aad.service", { + electron: { + dialog: dialogSpy, + }, + }); + + service = new mockAADService.AADService( + appSpy, localStorage, propertiesSpy, telemetryManagerSpy, ipcMainMock); + + service.init(); + }); + + it("when there is no item in the localstorage it should not set the id_token", () => { + localStorage.removeItem(Constants.localStorageKey.currentUser); + const tmpService = new AADService( + appSpy, localStorage, propertiesSpy, telemetryManagerSpy, ipcMainMock); + tmpService.init(); + let user: AADUser | null = null; + tmpService.currentUser.subscribe(x => user = x); + expect(user).toBeNull(); + }); + + it("when localstorage has currentUser it should load it", async (done) => { + await localStorage.setItem(Constants.localStorageKey.currentUser, JSON.stringify(sampleUser)); + const tmpService = new AADService( + appSpy, localStorage, propertiesSpy, telemetryManagerSpy, ipcMainMock); + await tmpService.init(); + let user: AADUser | null = null; + tmpService.currentUser.subscribe(x => user = x); + expect(user).not.toBeNull(); + expect(user.upn).toEqual("frank.smith@example.com"); + done(); + }); + + describe("Login", () => { + beforeEach(() => { + const newToken = new AccessToken({ + access_token: "newToken", expires_on: DateTime.local().plus({ hours: 1 }), + } as any); + spyOn(service, "accessTokenData").and.returnValue(new Promise((resolve) => resolve(newToken))); + }); + + it("login to public cloud", async () => { + await service.login().done; + expect(dialogSpy.showMessageBox).not.toHaveBeenCalled(); + }); + + it("login to national cloud", async () => { + propertiesSpy.azureEnvironment = AzureChina; + await service.login().done; + expect(dialogSpy.showMessageBox).toHaveBeenCalledTimes(1); + expect(dialogSpy.showMessageBox).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/client/core/aad/adal/aad.service.ts b/src/client/core/aad/auth/aad.service.ts similarity index 62% rename from src/client/core/aad/adal/aad.service.ts rename to src/client/core/aad/auth/aad.service.ts index d962e09932..2494e2d6f5 100644 --- a/src/client/core/aad/adal/aad.service.ts +++ b/src/client/core/aad/auth/aad.service.ts @@ -1,13 +1,13 @@ import { Inject, Injectable, forwardRef } from "@angular/core"; -import { AccessToken, AccessTokenCache, DataStore, ServerError } from "@batch-flask/core"; +import { AccessToken, DataStore, ServerError } from "@batch-flask/core"; import { log } from "@batch-flask/utils"; +import { TenantDetails } from "app/models"; import { AADResourceName, AzurePublic } from "client/azure-environment"; import { BatchExplorerApplication } from "client/core/batch-explorer-application"; import { BlIpcMain } from "client/core/bl-ipc-main"; import { fetch } from "client/core/fetch"; import { BatchExplorerProperties } from "client/core/properties"; -import { SecureDataStore } from "client/core/secure-data-store"; import { TelemetryManager } from "client/core/telemetry"; import { Constants } from "common"; import { IpcEvent } from "common/constants"; @@ -15,14 +15,14 @@ import { Deferred } from "common/deferred"; import { dialog } from "electron"; import { BehaviorSubject, Observable } from "rxjs"; import { AADConfig } from "../aad-config"; +import AuthProvider from "../auth-provider"; import { - AccessTokenService, -} from "../access-token"; -import { AuthenticationService, AuthenticationState, AuthorizeResult, LogoutError } from "../authentication"; + AuthenticationService, AuthenticationState, AuthorizeResult, LogoutError +} from "../authentication"; import { AADUser } from "./aad-user"; import { UserDecoder } from "./user-decoder"; -const adalConfig: AADConfig = { +const aadConfig: AADConfig = { tenant: "common", clientId: "04b07795-8ddb-461a-bbee-02f9e1bf7b46", // Azure CLI redirectUri: "urn:ietf:wg:oauth:2.0:oob", @@ -33,38 +33,35 @@ const adalConfig: AADConfig = { export class AADService { public currentUser: Observable; - public tenantsIds: Observable; + public tenants: Observable; public userAuthorization: AuthenticationService; public authenticationState: Observable; private _authenticationState = new BehaviorSubject(null); - private _accessTokenService: AccessTokenService; private _userDecoder: UserDecoder; private _newAccessTokenSubject: StringMap> = {}; private _currentUser = new BehaviorSubject(null); - private _tenantsIds = new BehaviorSubject([]); - private _tokenCache: AccessTokenCache; + private _tenants = new BehaviorSubject([]); constructor( @Inject(forwardRef(() => BatchExplorerApplication)) private app: BatchExplorerApplication, private localStorage: DataStore, private properties: BatchExplorerProperties, private telemetryManager: TelemetryManager, - secureStore: SecureDataStore, - ipcMain: BlIpcMain) { - this._tokenCache = new AccessTokenCache(secureStore); + ipcMain: BlIpcMain + ) { this._userDecoder = new UserDecoder(); this.currentUser = this._currentUser.asObservable(); - this.tenantsIds = this._tenantsIds.asObservable(); - this.userAuthorization = new AuthenticationService(this.app, adalConfig); - this._accessTokenService = new AccessTokenService(properties, adalConfig); + this.tenants = this._tenants.asObservable(); + this.userAuthorization = new AuthenticationService(this.app, aadConfig, + new AuthProvider(this.app, aadConfig)); this.authenticationState = this._authenticationState.asObservable(); - ipcMain.on(IpcEvent.AAD.accessTokenData, ({ tenantId, resource }) => { - return this.accessTokenData(tenantId, resource); - }); + ipcMain.on(IpcEvent.AAD.accessTokenData, + async ({ tenantId, resource }) => + await this.accessTokenData(tenantId, resource)); this.userAuthorization.state.subscribe((state) => { this._authenticationState.next(state); @@ -72,16 +69,12 @@ export class AADService { } public async init() { - await Promise.all([ - this._retrieveUserFromLocalStorage(), - this._tokenCache.init(), - ]); + await this._retrieveUserFromLocalStorage(); } /** * Login to azure active directory. * This will retrieve fresh tokens for all tenant and resources needed by BatchExplorer. - * It will try to use the refresh token cached to prevent a new prompt window if possible. */ public login(): { started: Promise, done: Promise } { const started = this._ensureTelemetryOptInNationalClouds(); @@ -93,8 +86,7 @@ export class AADService { public async logout() { await this.localStorage.removeItem(Constants.localStorageKey.currentUser); - this._tokenCache.clear(); - this._tenantsIds.next([]); + this._tenants.next([]); await this._clearUserSpecificCache(); for (const [, window] of this.app.windows) { window.webContents.session.clearStorageData({ storages: ["localStorage"] }); @@ -113,14 +105,7 @@ export class AADService { * @param resource */ public async accessTokenData(tenantId: string, resource?: AADResourceName): Promise { - resource = resource || "arm"; - if (this._tokenCache.hasToken(tenantId, resource)) { - const token = this._tokenCache.getToken(tenantId, resource); - if (!token.expireInLess(Constants.AAD.refreshMargin)) { - return token; - } - } - return this._retrieveNewAccessToken(tenantId, resource); + return this._retrieveNewAccessToken(tenantId, resource || "arm"); } private async _loginInCurrentCloud() { @@ -136,13 +121,12 @@ export class AADService { } } try { - const tenantIds = await this._loadTenantIds(); + const tenants = await this._loadTenants(); - this._tenantsIds.next(tenantIds); - this._refreshAllAccessTokens(); + this._tenants.next(tenants); } catch (error) { log.error("Error retrieving tenants", error); - this._tenantsIds.error(ServerError.fromARM(error)); + this._tenants.error(ServerError.fromARM(error)); } } @@ -162,20 +146,10 @@ export class AADService { } /** - * Retrieve a new access token using the refresh token if available or authorize the user and use authorization code - * Will set the currentAccesToken. + * Retrieve a new access token. * @return Observable with access token object */ private async _retrieveNewAccessToken(tenantId: string, resource: AADResourceName): Promise { - const token = this._tokenCache.getToken(tenantId, resource); - if (token && token.refresh_token) { - return this._useRefreshToken(tenantId, resource, token.refresh_token); - } - - if (resource in this._newAccessTokenSubject) { - return this._newAccessTokenSubject[this._tenantResourceKey(tenantId, resource)].promise; - } - const defer = new Deferred(); this._newAccessTokenSubject[this._tenantResourceKey(tenantId, resource)] = defer; this._redeemNewAccessToken(tenantId, resource); @@ -189,18 +163,28 @@ export class AADService { /** * Load a new access token from the authorization code given at login */ - private async _redeemNewAccessToken(tenantId: string, resource: AADResourceName, forceReLogin = false) { + private async _redeemNewAccessToken(tenantId: string, resource: AADResourceName) { const defer = this._newAccessTokenSubject[this._tenantResourceKey(tenantId, resource)]; try { - - const result = await this._authorizeUser(tenantId, forceReLogin); - this._processUserToken(result.id_token); - const tid = tenantId === "common" ? this._currentUser.value!.tid : tenantId; - const token = await this._accessTokenService.redeem(resource, tid!, result.code); - this._processAccessToken(tenantId, resource, token); + const result: AuthorizeResult = + await this.userAuthorization.authorizeResource( + tenantId, + this.properties.azureEnvironment[resource as string] + ); + this._processUserToken(result.idToken); delete this._newAccessTokenSubject[this._tenantResourceKey(tenantId, resource)]; - defer.resolve(token); + defer.resolve(new AccessToken({ + access_token: result.accessToken, + refresh_token: null, + token_type: result.tokenType, + expires_in: null, + expires_on: result.expiresOn, + ext_expires_in: null, + not_before: null, + tenantId, + resource + })); } catch (e) { log.error(`Error redeem auth code for a token for resource ${resource}`, e); @@ -209,35 +193,6 @@ export class AADService { } } - private async _authorizeUser(tenantId, forceReLogin): Promise { - if (forceReLogin) { - return this.userAuthorization.authorize(tenantId, false); - } else { - return this.userAuthorization.authorizeTrySilentFirst(tenantId); - } - } - - /** - * Use the refresh token to get a new access token - * @param tenantId TenantId to access - * @param resource Resource to access - * @param refreshToken Refresh token - */ - private async _useRefreshToken( - tenantId: string, - resource: AADResourceName, - refreshToken: string): Promise { - try { - const token = await this._accessTokenService.refresh(resource, tenantId, refreshToken); - this._processAccessToken(tenantId, resource, token); - return token; - } catch (error) { - log.warn("Refresh token is not valid", error); - this._tokenCache.removeToken(tenantId, resource); - return this._retrieveNewAccessToken(tenantId, resource); - } - } - /** * Process IDToken return by the /authorize url to extract user information */ @@ -251,44 +206,27 @@ export class AADService { this.localStorage.setItem(Constants.localStorageKey.currentUser, JSON.stringify(user)); } - private _processAccessToken(tenantId: string, resource: string, token: AccessToken) { - this._tokenCache.storeToken(tenantId, resource, token); - } - - private async _loadTenantIds(): Promise { + private async _loadTenants(): Promise { const token = await this.accessTokenData("common"); const headers = { Authorization: `${token.token_type} ${token.access_token}`, }; const options = { headers }; - const url = `${this.properties.azureEnvironment.arm}tenants?api-version=${Constants.ApiVersion.arm}`; + const url = this._tenantURL(); const response = await fetch(url, options); - log.info("Listing tenants response", response.status, response.statusText); const { value } = await response.json(); - return value.map(x => x.tenantId); + return value; + } + + private _tenantURL() { + return this.properties.azureEnvironment.arm + + `tenants?api-version=${Constants.ApiVersion.arm}`; } private async _clearUserSpecificCache() { this.localStorage.removeItem(Constants.localStorageKey.subscriptions); this.localStorage.removeItem(Constants.localStorageKey.selectedAccountId); - await this._tokenCache.clear(); - } - - private async _refreshAllAccessTokens() { - const tenantIds = this._tenantsIds.value; - for (const tenantId of tenantIds) { - for (const resource of this._resources()) { - await this._retrieveNewAccessToken(tenantId, resource); - } - } - } - - private _resources(): AADResourceName[] { - return [ - "arm", - "batch", - ]; } private async _ensureTelemetryOptInNationalClouds() { diff --git a/src/client/core/aad/adal/index.ts b/src/client/core/aad/auth/index.ts similarity index 100% rename from src/client/core/aad/adal/index.ts rename to src/client/core/aad/auth/index.ts diff --git a/src/client/core/aad/adal/user-decoder.ts b/src/client/core/aad/auth/user-decoder.ts similarity index 100% rename from src/client/core/aad/adal/user-decoder.ts rename to src/client/core/aad/auth/user-decoder.ts diff --git a/src/client/core/aad/authentication/authentication-window.ts b/src/client/core/aad/authentication/authentication-window.ts index 97e521348d..2915df8289 100644 --- a/src/client/core/aad/authentication/authentication-window.ts +++ b/src/client/core/aad/authentication/authentication-window.ts @@ -34,19 +34,19 @@ export class AuthenticationWindow extends UniqueWindow { } public onRedirect(callback: (newUrl: string) => void) { - this._window!.webContents.session.webRequest.onBeforeRedirect((details) => { + this._window.webContents.session.webRequest.onBeforeRedirect((details) => { callback(details.redirectURL); }); } public onNavigate(callback: (url: string) => void) { - this._window!.webContents.on("did-navigate", (event, url) => { + this._window.webContents.on("did-navigate", (event, url) => { callback(url); }); } public onError(callback: (val: { code: number, description: string }) => void) { - this._window!.webContents.on("did-fail-load", ( + this._window.webContents.on("did-fail-load", ( e, errorCode: number, errorDescription: string, @@ -62,12 +62,12 @@ export class AuthenticationWindow extends UniqueWindow { } public onClose(callback: () => void) { - this._window!.on("close", (event) => { + this._window.on("close", (event) => { callback(); }); } public clearCookies() { - this._window!.webContents.session.clearStorageData({ storages: ["cookies"] }); + this._window.webContents.session.clearStorageData({ storages: ["cookies"] }); } } diff --git a/src/client/core/aad/authentication/authentication.service.spec.ts b/src/client/core/aad/authentication/authentication.service.spec.ts index 2bb3f4196c..696ecb3ae9 100644 --- a/src/client/core/aad/authentication/authentication.service.spec.ts +++ b/src/client/core/aad/authentication/authentication.service.spec.ts @@ -1,12 +1,21 @@ import { AzurePublic } from "client/azure-environment"; +import { MockAuthProvider } from "test/utils/mocks/auth"; import { MockAuthenticationWindow, MockSplashScreen } from "test/utils/mocks/windows"; import { AuthenticationService, AuthenticationState, AuthorizeError, AuthorizeResult, } from "./authentication.service"; +const CONFIG = { + tenant: "common", + clientId: "abc", + redirectUri: "http://localhost", + logoutRedirectUri: "http://localhost", +}; + describe("AuthenticationService", () => { let userAuthorization: AuthenticationService; let fakeAuthWindow: MockAuthenticationWindow; + let fakeAuthProvider: MockAuthProvider; let appSpy; let state: AuthenticationState; beforeEach(() => { @@ -17,13 +26,9 @@ describe("AuthenticationService", () => { azureEnvironment: AzurePublic, }, }; - const config = { - tenant: "common", - clientId: "abc", - redirectUri: "http://localhost", - logoutRedirectUri: "http://localhost", - }; - userAuthorization = new AuthenticationService(appSpy, config); + fakeAuthProvider = new MockAuthProvider(appSpy, CONFIG); + userAuthorization = new AuthenticationService(appSpy, CONFIG, + fakeAuthProvider); fakeAuthWindow = appSpy.authenticationWindow; userAuthorization.state.subscribe(x => state = x); }); @@ -32,42 +37,32 @@ describe("AuthenticationService", () => { let result: AuthorizeResult | null; let error: AuthorizeError | null; let promise; - beforeEach(() => { + beforeEach(async () => { result = null; error = null; const obs = userAuthorization.authorize("tenant-1"); promise = obs.then((out) => result = out).catch((e) => error = e); }); - it("Should have called loadurl", () => { + it("Should have called loadurl", async () => { + fakeAuthWindow.notifyRedirect(CONFIG.redirectUri); + await promise; + expect(fakeAuthWindow.loadURL).toHaveBeenCalledTimes(1); const args = fakeAuthWindow.loadURL.calls.mostRecent().args; expect(args.length).toBe(1); - const url = args[0]; - expect(url).toContain("https://login.microsoftonline.com/tenant-1/oauth2/authorize"); - expect(url).toContain("&resource=https://management.azure.com/"); - expect(url).toContain("?response_type=id_token+code"); - expect(url).toContain("&scope=user_impersonation+openid"); - expect(url).toContain("&client_id=abc"); - expect(url).toContain("&redirect_uri=http%3A%2F%2Flocalhost"); - expect(url).not.toContain("&prompt=none"); }); it("window should be visible", () => { expect(fakeAuthWindow.isVisible()).toBe(true); }); - it("state should now be UserInput", () => { - expect(state).toBe(AuthenticationState.UserInput); - }); - - it("Should return the id token and code when sucessfull", async () => { - const newUrl = "http://localhost/#id_token=sometoken&code=somecode"; - fakeAuthWindow.notifyRedirect(newUrl); + it("should return the id token and code when successful", async () => { + fakeAuthWindow.notifyRedirect(CONFIG.redirectUri); + fakeAuthProvider.fakeToken = { accessToken: "somecode" }; await promise; expect(result).not.toBeNull(); - expect(result!.id_token).toEqual("sometoken"); - expect(result!.code).toEqual("somecode"); + expect(result.accessToken).toEqual("somecode"); expect(error).toBeNull(); expect(fakeAuthWindow.destroy).toHaveBeenCalledTimes(1); @@ -80,21 +75,24 @@ describe("AuthenticationService", () => { expect(result).toBeNull(); expect(error).not.toBeNull(); - expect(error!.error).toEqual("Failed to authenticate"); - expect(error!.description).toEqual("Failed to load the AAD login page (4:Foo bar)"); + expect(error.error).toEqual("Failed to authenticate"); + expect(error.description).toEqual("Failed to load the AAD login page (4:Foo bar)"); expect(fakeAuthWindow.destroy).toHaveBeenCalledTimes(1); }); it("Should error when the url redirect returns an error", async () => { - const newUrl = "http://localhost/#error=someerror&error_description=There was an error"; - fakeAuthWindow.notifyRedirect(newUrl); + fakeAuthWindow.notifyRedirect(CONFIG.redirectUri); + fakeAuthProvider.fakeError = { + error: "someerror", + description: "There was an error" + } await promise; expect(result).toBeNull(); expect(error).not.toBeNull(); - expect(error!.error).toEqual("someerror"); - expect(error!.description).toEqual("There was an error"); + expect(error.error).toEqual("someerror"); + expect(error.description).toEqual("There was an error"); expect(fakeAuthWindow.destroy).toHaveBeenCalledTimes(1); }); @@ -110,42 +108,43 @@ describe("AuthenticationService", () => { expect(tenant1Spy).not.toHaveBeenCalled(); expect(tenant2Spy).not.toHaveBeenCalled(); - const newUrl1 = "http://localhost/#id_token=sometoken&code=somecode"; - fakeAuthWindow.notifyRedirect(newUrl1); + fakeAuthWindow.notifyRedirect(CONFIG.redirectUri); + fakeAuthProvider.fakeToken = { accessToken: "somecode" }; await p1; // Should have set tenant-1 expect(result).not.toBeNull(); - expect(result!.id_token).toEqual("sometoken"); - expect(result!.code).toEqual("somecode"); + expect(result.accessToken).toEqual("somecode"); expect(fakeAuthWindow.destroy).toHaveBeenCalledTimes(1); expect(tenant1Spy).toHaveBeenCalled(); - expect(tenant1Spy).toHaveBeenCalledWith({ id_token: "sometoken", code: "somecode" }); + expect(tenant1Spy).toHaveBeenCalledWith({ + accessToken: "somecode", + id_token: null, + code: null, + session_state: null + }); expect(tenant2Spy).not.toHaveBeenCalled(); // Should now authorize for tenant-2 - const newUrl2 = "http://localhost/#id_token=sometoken2&code=somecode2"; - fakeAuthWindow.notifyRedirect(newUrl2); + fakeAuthWindow.notifyRedirect(CONFIG.redirectUri); await p2; expect(tenant2Spy).toHaveBeenCalled(); - expect(tenant2Spy).toHaveBeenCalledWith({ id_token: "sometoken2", code: "somecode2" }); + expect(tenant2Spy).toHaveBeenCalledWith({ + accessToken: "somecode", + id_token: null, + code: null, + session_state: null + }); expect(fakeAuthWindow.destroy).toHaveBeenCalledTimes(2); }); }); describe("Authorize silently", () => { beforeEach(() => { - userAuthorization.authorize("tenant-1", true); - }); - - it("should set the prompt=none params", () => { - const args = fakeAuthWindow.loadURL.calls.mostRecent().args; - expect(args.length).toBe(1); - const url = args[0]; - expect(url).toContain("&prompt=none"); + userAuthorization.authorize("tenant-1"); }); it("shoud not be visible", () => { @@ -186,31 +185,7 @@ describe("AuthenticationService", () => { expect(error).toBeNull(); expect(userAuthorization.authorize).toHaveBeenCalledTimes(1); - expect(userAuthorization.authorize).toHaveBeenCalledWith("tenant-1", true); - }); - - it("Should call silent false if silent true return sucessfully", async () => { - authorizeOutput = jasmine.createSpy("output").and.returnValues( - Promise.reject(badResult), - Promise.resolve(goodResult)); - callAuth(); - await promise; - expect(result).toEqual(goodResult); - expect(error).toBeNull(); - - expect(userAuthorization.authorize).toHaveBeenCalledTimes(2); - expect(userAuthorization.authorize).toHaveBeenCalledWith("tenant-1", true); - expect(userAuthorization.authorize).toHaveBeenCalledWith("tenant-1", false); - }); - - it("Should return error if both silent true and false return an error", async () => { - authorizeOutput = jasmine.createSpy("output").and.returnValue(Promise.reject(badResult)); - callAuth(); - await promise; - - expect(userAuthorization.authorize).toHaveBeenCalledTimes(2); - expect(result).toBeNull(); - expect(error).toEqual(badResult); + expect(userAuthorization.authorize).toHaveBeenCalledWith("tenant-1"); }); }); }); diff --git a/src/client/core/aad/authentication/authentication.service.ts b/src/client/core/aad/authentication/authentication.service.ts index 7153e35c11..96cc5c5aff 100644 --- a/src/client/core/aad/authentication/authentication.service.ts +++ b/src/client/core/aad/authentication/authentication.service.ts @@ -1,23 +1,23 @@ -import { SanitizedError, SecureUtils } from "@batch-flask/utils"; +import { SanitizedError } from "@batch-flask/utils"; import { BatchExplorerApplication } from "client/core/batch-explorer-application"; import { Deferred } from "common"; import { BehaviorSubject, Observable } from "rxjs"; import { AADConfig } from "../aad-config"; -import * as AdalConstants from "../adal-constants"; +import * as AADConstants from "../aad-constants"; +import AuthProvider, { AuthorizationResult } from "../auth-provider"; -enum AuthorizePromptType { - login = "login", - none = "none", - consent = "consent", -} - -export interface AuthorizeResult { +export interface AuthorizeResult extends AuthorizationResult { code: string; id_token: string; session_state: string; state: string; } +type AuthCodeResult = { + code?: string; + client_info?: string; +} & AuthorizeResponseError; + export interface AuthorizeResponseError { error: string; error_description: string; @@ -30,7 +30,7 @@ export class AuthorizeError extends Error { public state?: string; constructor(error: AuthorizeResponseError) { - const description = error.error_description && error.error_description.replace(/\+/g, " "); + const description = error.error_description?.replace(/\+/g, " "); super(`${error.error}: ${description}`); this.error = error.error; this.description = description; @@ -40,7 +40,7 @@ export class AuthorizeError extends Error { interface AuthorizeQueueItem { tenantId: string; - silent: boolean; + resourceURI: string; deferred: Deferred; } @@ -61,28 +61,42 @@ export class LogoutError extends SanitizedError { */ export class AuthenticationService { public state: Observable; - private _waitingForAuth = false; - private _authorizeQueue: AuthorizeQueueItem[] = []; - private _currentAuthorization: AuthorizeQueueItem | null = null; + private _authQueue = new AuthorizeQueue(); private _state = new BehaviorSubject(AuthenticationState.None); private _logoutDeferred: Deferred | null; - constructor(private app: BatchExplorerApplication, private config: AADConfig) { + constructor( + private app: BatchExplorerApplication, + private config: AADConfig, + private authProvider: AuthProvider + ) { this.state = this._state.asObservable(); } + /** - * Authorize the user. - * @param silent If set to true it will not ask the user for prompt. (i.e prompt=none for AD) - * This means the request will fail if the user didn't give consent yet or it expired + * Authorize the user against the specified tenant. * @returns Observable with the successful AuthorizeResult. - * If silent is true and the access fail the observable will return and error of type AuthorizeError */ - public async authorize(tenantId: string, silent = false): Promise { - if (this._isAuthorizingTenant(tenantId)) { - return this._getTenantDeferred(tenantId).promise; + public async authorize(tenantId: string): Promise { + return this.authorizeResource( + tenantId, this.app.properties.azureEnvironment.arm + ); + } + + /** + * Authorizes a resource + * + * @param tenantId The tenant ID + * @param resourceURI The resource URI + * @returns a promise with AuthorizedResult + */ + public async authorizeResource(tenantId: string, resourceURI: string): + Promise { + const existingAuth = this._authQueue.get(tenantId, resourceURI); + if (existingAuth) { + return existingAuth.deferred.promise; } - const deferred = new Deferred(); - this._authorizeQueue.push({ tenantId, silent, deferred }); + const deferred = this._authQueue.add(tenantId, resourceURI); this._authorizeNext(); return deferred.promise; } @@ -91,94 +105,87 @@ export class AuthenticationService { * This will try to do authorize silently first and if it fails show the login window to the user */ public async authorizeTrySilentFirst(tenantId: string): Promise { - return this.authorize(tenantId, true).catch((error) => { - if (error instanceof LogoutError) { - throw error; - } - return this.authorize(tenantId, false); - }); + return this.authorize(tenantId); } /** * Log the user out */ public async logout() { - this._waitingForAuth = true; - if (this._logoutDeferred) { return this._logoutDeferred.promise; } - const url = AdalConstants.logoutUrl(this.app.properties.azureEnvironment.aadUrl, this.config.tenant); - const authWindow = this.app.authenticationWindow; - authWindow.create(); - this._setupEvents(); - if (this._currentAuthorization) { - this._currentAuthorization.deferred.reject(new LogoutError()); - } - this._currentAuthorization = null; - this._authorizeQueue.forEach(x => x.deferred.reject(new LogoutError())); - this._authorizeQueue = []; - authWindow.clearCookies(); - authWindow.loadURL(url); + this.authProvider.logout(); + this._authQueue.clear(); + + const url = AADConstants.logoutUrl(this.app.properties.azureEnvironment.aadUrl, this.config.tenant); + this._loadAuthWindow(url, { clear: true }); const deferred = this._logoutDeferred = new Deferred(); return deferred.promise; } - private _authorizeNext() { - if (this._currentAuthorization || this._authorizeQueue.length === 0) { + private async _authorizeNext() { + if (this._authQueue.authInProgress()) { return; } - this._waitingForAuth = true; - const { tenantId, silent } = this._currentAuthorization = this._authorizeQueue.shift()!; - const authWindow = this.app.authenticationWindow; - authWindow.create(); - authWindow.loadURL(this._buildUrl(tenantId, silent)); - this._setupEvents(); + const { tenantId, resourceURI, deferred } = this._authQueue.shift(); - if (!silent) { - authWindow.show(); - this._state.next(AuthenticationState.UserInput); + try { + const authResult: AuthorizationResult = + await this.authProvider.getToken({ + resourceURI, + tenantId, + authCodeCallback: (url, silent) => + this._authorizationCodeCallback(url, silent) + }); + + if (this._state.getValue() !== AuthenticationState.Authenticated) { + this._state.next(AuthenticationState.Authenticated); + } + + deferred.resolve({ + ...authResult, + code: null, + id_token: null, + session_state: null + }); + } catch (e) { + deferred.reject(e); + } finally { + this._authQueue.current = null; + this._authorizeNext(); } } - /** - * Return the url used to authorize - * @param silent @see #authorize - */ - private _buildUrl(tenantId, silent: boolean): string { - const params: AdalConstants.AuthorizeUrlParams = { - response_type: "id_token+code", - redirect_uri: encodeURIComponent(this.config.redirectUri), - client_id: this.config.clientId, - scope: "user_impersonation+openid", - nonce: SecureUtils.uuid(), - state: SecureUtils.uuid(), - resource: this.app.properties.azureEnvironment.arm, - }; - - if (silent) { - params.prompt = AuthorizePromptType.none; - } - return AdalConstants.authorizeUrl(this.app.properties.azureEnvironment.aadUrl, tenantId, params); + private async _authorizationCodeCallback(url: string, silent = false) { + this._state.next(AuthenticationState.UserInput); + return await this._loadAuthWindow(url, { show: !silent }); } - /** - * Setup event listener on the current window - */ - private _setupEvents() { + private _loadAuthWindow(url: string, { clear = false, show = true } = {}) { const authWindow = this.app.authenticationWindow; - authWindow.onRedirect(newUrl => this._handleCallback(newUrl)); + const deferred = new Deferred(); + authWindow.create(); + authWindow.onRedirect(newUrl => + this._authWindowRedirected(newUrl, deferred)); authWindow.onNavigate(newUrl => this._handleNavigate(newUrl)); - authWindow.onError((error) => { - this._handleError(error); - }); + authWindow.onError(error => this._handleError(error)); + + if (clear) { + authWindow.clearCookies(); + } + + authWindow.loadURL(url); + if (show) { + authWindow.show(); + } + return deferred.promise; } private _handleNavigate(url: string) { - if (this._logoutDeferred && url.endsWith("oauth2/logout")) { + if (this._logoutDeferred && AADConstants.isLogoutURL(url)) { this._closeWindow(); - this._waitingForAuth = false; const deferred = this._logoutDeferred; this._logoutDeferred = null; deferred.resolve(); @@ -189,34 +196,27 @@ export class AuthenticationService { * Get called when the auth window redirect or navigate somewhere. * This is used to catch the final redirect_uri of AD' * @param url Url used for callback + * @param callback Called with the result of the auth code */ - private _handleCallback(url: string) { - if (!this._isRedirectUrl(url) || !this._currentAuthorization) { + private _authWindowRedirected(url: string, deferred: Deferred) { + if (!this._isRedirectUrl(url) || !this._authQueue.hasCurrent()) { return; } this._closeWindow(); - const params = this._getRedirectUrlParams(url); - this._waitingForAuth = false; - const auth = this._currentAuthorization; - this._currentAuthorization = null; - - if ((params as any).error) { - auth.deferred.reject(new AuthorizeError(params as AuthorizeResponseError)); + const params: AuthCodeResult = this._getRedirectUrlParams(url); + if (params.error) { + deferred.reject( + new AuthorizeError(params as AuthorizeResponseError) + ); } else { - this._state.next(AuthenticationState.Authenticated); - auth.deferred.resolve(params as AuthorizeResult); + deferred.resolve(params.code); } - this._authorizeNext(); } private _handleError({code, description}) { this._closeWindow(); - this._waitingForAuth = false; - const auth = this._currentAuthorization; - if (!auth) { return; } - this._currentAuthorization = null; - auth.deferred.reject(new AuthorizeError({ + this._authQueue.rejectCurrent(new AuthorizeError({ error: "Failed to authenticate", error_description: `Failed to load the AAD login page (${code}:${description})`, })); @@ -232,36 +232,75 @@ export class AuthenticationService { /** * Extract params return in the redirect_uri */ - private _getRedirectUrlParams(url: string): AuthorizeResult | AuthorizeResponseError { - const segments = url.split("#"); + private _getRedirectUrlParams(url: string): AuthCodeResult | AuthorizeResponseError { + const parsedUrl = new URL(url); const params = {}; - for (const str of segments[1].split("&")) { - const [key, value] = str.split("="); - params[key] = decodeURIComponent(value); - } + parsedUrl.searchParams.forEach((value: string, key: string) => { + params[key] = value + }); return params as any; } - private _isAuthorizingTenant(tenantId: string) { - return Boolean(this._getTenantAuthorization(tenantId)); + private _closeWindow() { + const window = this.app.authenticationWindow; + if (window) { + window.destroy(); + } } +} - private _getTenantAuthorization(tenantId: string): AuthorizeQueueItem { - if (this._currentAuthorization && this._currentAuthorization.tenantId === tenantId) { - return this._currentAuthorization; - } - return this._authorizeQueue.filter(x => x.tenantId === tenantId)[0]; +/** + * Manages multiple authorization requests + */ +class AuthorizeQueue { + private queue: AuthorizeQueueItem[] = []; + public current: AuthorizeQueueItem = null; + + public clear() { + this.queue.forEach( + auth => auth.deferred.reject(new LogoutError())); + this.queue.length = 0; + this.current = null; } - private _getTenantDeferred(tenantId: string): Deferred { - const auth = this._getTenantAuthorization(tenantId); - return auth && auth.deferred; + public add(tenantId: string, resourceURI: string) { + const deferred = new Deferred(); + this.queue.push({ tenantId, resourceURI, deferred }); + return deferred; } - private _closeWindow() { - const window = this.app.authenticationWindow; - if (window) { - window.destroy(); + public shift(): AuthorizeQueueItem { + this.current = this.queue.shift(); + return this.current; + } + + public authInProgress() { + return this.hasCurrent() || this.queue.length === 0; + } + + public resolveCurrent(callback) { + const deferred = this.current.deferred; + this.current.deferred = null; + deferred.resolve(callback); + return deferred; + } + + public rejectCurrent(callback) { + const deferred = this.current.deferred; + this.current.deferred = null; + deferred.reject(callback); + return deferred; + } + + public get(tenantId: string, resourceURI: string) { + if (this.current?.tenantId === tenantId && this.current?.resourceURI) { + return this.current; } + return this.queue.filter(auth => + auth.tenantId === tenantId && auth.resourceURI === resourceURI)[0]; + } + + public hasCurrent() { + return !!this.current; } } diff --git a/src/client/core/aad/index.ts b/src/client/core/aad/index.ts index 28db55ebd8..ae7305cd7e 100644 --- a/src/client/core/aad/index.ts +++ b/src/client/core/aad/index.ts @@ -1,2 +1,2 @@ export * from "./authentication"; -export * from "./adal"; +export * from "./auth"; diff --git a/src/common/constants/constants.ts b/src/common/constants/constants.ts index ba396c4c2a..24f4500485 100644 --- a/src/common/constants/constants.ts +++ b/src/common/constants/constants.ts @@ -99,7 +99,7 @@ export const localStorageKey = { }; export const ApiVersion = { - arm: "2016-09-01", + arm: "2020-01-01", armClassicStorage: "2016-11-01", armStorage: "2016-12-01", armBatch: "2019-08-01", diff --git a/src/test/utils/mocks/auth/auth-provider.mock.ts b/src/test/utils/mocks/auth/auth-provider.mock.ts new file mode 100644 index 0000000000..0b6da34512 --- /dev/null +++ b/src/test/utils/mocks/auth/auth-provider.mock.ts @@ -0,0 +1,52 @@ +import { AuthenticationResult, ClientApplication } from "@azure/msal-node"; +import { AuthorizeError } from "client/core/aad"; +import { AADConfig } from "client/core/aad/aad-config"; +import AuthProvider from "client/core/aad/auth-provider"; + +export class MockAuthProvider extends AuthProvider { + public fakeToken: Partial; + public fakeConfig: AADConfig; + public fakeError: Partial; + constructor(app: any, config: AADConfig) { + super(app, config); + this.fakeConfig = config; + this._getClient = jasmine.createSpy("_getClient").and.returnValue( + new MockClientApplication(this) + ); + } +} +export class MockClientApplication extends ClientApplication { + constructor(public fakeAuthProvider: MockAuthProvider) { + super({ + auth: { + clientId: fakeAuthProvider.fakeConfig.clientId + } + }); + } + + public getAuthCodeUrl(request) { + if (request?.prompt === "none") { + throw "No silent auth"; + } + return Promise.resolve(this.fakeAuthProvider.fakeConfig.redirectUri); + } + + public acquireTokenByCode() { + if (this.fakeAuthProvider.fakeError) { + return Promise.reject(this.fakeAuthProvider.fakeError); + } + return Promise.resolve( + this.fakeAuthProvider.fakeToken as AuthenticationResult); + } + +} + +export const createMockClientApplication = () => { + const fakeAuthProvider = new MockAuthProvider({}, { + tenant: null, + clientId: null, + redirectUri: null, + logoutRedirectUri: null + }); + return new MockClientApplication(fakeAuthProvider); +}; diff --git a/src/test/utils/mocks/auth/index.ts b/src/test/utils/mocks/auth/index.ts new file mode 100644 index 0000000000..3c7089332d --- /dev/null +++ b/src/test/utils/mocks/auth/index.ts @@ -0,0 +1 @@ +export * from "./auth-provider.mock"; diff --git a/test/app/spec-reporters.ts b/test/app/spec-reporters.ts index fefce19275..b3cfd539b9 100644 --- a/test/app/spec-reporters.ts +++ b/test/app/spec-reporters.ts @@ -52,13 +52,6 @@ if (process.env.DEBUG_TIME) { jasmine.getEnv().addReporter({ jasmineDone: (result) => { - // KLUDGE: Delete the sort function from the result to prevent errors - // when sending it over IPC. This may be fixed in a later - // version of karma-jasmine or karma-electron (in which case - // this line may be removed) - // See: https://github.com/twolfson/karma-electron/issues/47 - delete result.order.sort; - console.log("Total memory is", chromePerformance.memory.usedJSHeapSize); setTimeout(() => { diff --git a/test/spectron/test-app-start.spec.ts b/test/spectron/test-app-start.spec.ts index 6ff8f9e5ea..4e6855a72a 100644 --- a/test/spectron/test-app-start.spec.ts +++ b/test/spectron/test-app-start.spec.ts @@ -11,7 +11,7 @@ describe("Bundled application is starting correctly", () => { app = new Application({ path: getExePath(), args: ["--insecure-test"], - startTimeout: 20000, + startTimeout: 3000, waitTimeout: 20000, }); @@ -29,16 +29,15 @@ describe("Bundled application is starting correctly", () => { }); it("Show the splash screen, auth window then login", async () => { - const windowCount = await app.client.getWindowCount(); - // Splash screen + Auth window + Main window - expect(windowCount).toEqual(3); + await delay(2_000); - const windows = await getWindowIndices(app); + // Splash screen + Auth window + Main window + expect(await app.client.getWindowCount()).toEqual(3); - await app.client.windowByIndex(windows.main); + await switchToWindow(app, WindowType.main); expect(await app.browserWindow.isVisible()).toBe(false); - await app.client.windowByIndex(windows.splash); + await switchToWindow(app, WindowType.splash); expect(await app.browserWindow.isVisible()).toBe(true); expect(await app.browserWindow.getBounds()).toEqual(jasmine.objectContaining({ width: 340, @@ -46,7 +45,7 @@ describe("Bundled application is starting correctly", () => { })); await app.client.waitUntilTextExists("#message", "Prompting for user input"); - await app.client.windowByIndex(windows.auth); + await switchToWindow(app, WindowType.auth); expect(await app.browserWindow.isVisible()).toBe(true); expect(await app.browserWindow.getTitle()).toEqual("BatchExplorer: Login to Azure Public(Default)"); @@ -60,8 +59,7 @@ describe("Bundled application is starting correctly", () => { return await app.client.getWindowCount() === 1; }); - // Switch to main window which is now at index 0 - await app.client.windowByIndex(0); + await switchToWindow(app, WindowType.main); expect(await app.browserWindow.isVisible()).toBe(true); expect(await app.browserWindow.getTitle()).toEqual("Batch Explorer"); @@ -93,9 +91,16 @@ async function signIn(client: SpectronClient) { await delay(5000); const url = await client.getUrl(); - if (url.startsWith("https://msft.sts.microsoft.com")) { + + if (url.startsWith("https://login.microsoftonline.com/common/oauth2/v2.0/authorize")) { + // Click on "Sign with email or passwork instead" + const signInWithEmailOrPasswordLink = + await client.$("#redirectToIdpLink"); + await signInWithEmailOrPasswordLink.click(); + } else if (url.startsWith("https://msft.sts.microsoft.com")) { // Click on "Sign with email or passwork instead" - const signInWithEmailOrPasswordLink = await client.$("#authOptions .optionButton"); + const signInWithEmailOrPasswordLink = + await client.$("#authOptions .optionButton"); await signInWithEmailOrPasswordLink.click(); } @@ -110,44 +115,32 @@ function delay(time?: number) { return new Promise(r => setTimeout(r, time)); } -interface WindowIndices { - splash: number; - main: number; - auth: number; +enum WindowType { + auth = "auth", + splash = "splash", + main = "main" } -/** - * Gets a map containing the index of each window in the application (splash screen, - * auth window, main window). The order of these windows seems to vary - * between Windows and MacOS, so use the URLs to detect which is which. - */ -async function getWindowIndices(app: Application): Promise { - const allWindows = [ - await app.client.windowByIndex(0), - await app.client.windowByIndex(1), - await app.client.windowByIndex(2), - ]; - - const windows: WindowIndices = { - splash: -1, - main: -1, - auth: -1, - }; - - for (let i = 0; i < allWindows.length; i++) { +async function switchToWindow(app: Application, type: WindowType) { + const count = await app.client.getWindowCount(); + const matcher = windowMatcher(type); + for (let i = 0; i < count; i++) { await app.client.windowByIndex(i); - const url = await app.client.getUrl(); - if (url.endsWith("index.html#/accounts")) { - windows.main = i; - } else if (url.endsWith("splash-screen.html")) { - windows.splash = i; - } else if (url.startsWith("https://login.microsoftonline.com")) { - windows.auth = i; - } else { - throw new Error("Unknown window URL: " + url); + if (url && matcher(url)) { + return; } } + throw new Error(`Could not find window ${type}`); + } - return windows; +function windowMatcher(type: WindowType) { + switch (type) { + case WindowType.main: + return url => url.endsWith("index.html#/accounts"); + case WindowType.splash: + return url => url.endsWith("splash-screen.html"); + case WindowType.auth: + return url => url.startsWith("https://login.microsoftonline.com"); + } } diff --git a/tsconfig.test.json b/tsconfig.test.json index 2639ab898a..162e5494b8 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -8,6 +8,7 @@ "app/**/*.ts", "app/**/*.spec.ts", "src/common/**/*.ts", + "src/test/**/*.ts", "test/**/*.ts", "**/testing/**/*", ],