diff --git a/.circleci/config.yml b/.circleci/config.yml index 8ef343b5..23f4f419 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,11 @@ version: 2.1 +parameters: + run_nightly: + default: false + type: boolean orbs: win: circleci/windows@1.0.0 - aws-s3: circleci/aws-s3@1.0.11 + aws-s3: circleci/aws-s3@2.0.0 owasp: entur/owasp@0.0.10 executors: @@ -44,7 +48,6 @@ commands: - run: command: | export VERSION=$(jq -r .version package.json) - echo "payload=" > /tmp/webhook-data.json; echo '{}' | jq "{ \"username\": \"<< parameters.username >>\", \"icon_url\": \"<< parameters.icon >>\", @@ -52,7 +55,7 @@ commands: }" >> /tmp/webhook-data.json - run: command: | - curl -i -X POST -d @/tmp/webhook-data.json $MATTERMOST_RELEASE_WEBHOOK_URL_DESKTOP || echo "NOFICATION FAILED! check logs as this will succeed intentionally" + curl -i -H "Content-Type: application/json" -X POST -d @/tmp/webhook-data.json $MATTERMOST_RELEASE_WEBHOOK_URL_DESKTOP || echo "NOFICATION FAILED! check logs as this will succeed intentionally" update_image: description: "Update base image" @@ -105,8 +108,7 @@ jobs: apt_opts: "--no-install-recommends" - restore_cache: key: lint-{{ arch }}-{{ .Branch }}-{{ checksum "package-lock.json" }} - - run: npm run lint:js-quiet - - run: ELECTRON_DISABLE_SANDBOX=1 xvfb-run npm run test:app + - run: ELECTRON_DISABLE_SANDBOX=1 xvfb-run npm run test - run: mkdir -p /tmp/test-results - run: cp test-results.xml /tmp/test-results/ - store_test_results: @@ -115,68 +117,6 @@ jobs: key: lint-{{ arch }}-{{ .Branch }}-{{ checksum "package-lock.json" }} paths: - "node_modules" - - "src/node_modules" - - check-deps: - parameters: - cve_data_directory: - type: string - default: "~/.owasp/dependency-check-data" - working_directory: ~/mattermost/desktop - executor: owasp/default - environment: - version_url: "https://jeremylong.github.io/DependencyCheck/current.txt" - executable_url: "https://dl.bintray.com/jeremy-long/owasp/dependency-check-VERSION-release.zip" - steps: - - checkout - - run: - name: Link dependency cache - command: sudo ln -s ~/mattermost/desktop /root/mattermost-desktop; sudo chmod 777 /root - - restore_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "package-lock.json" }} - - restore_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "src/package-lock.json" }} - - run: - name: Adjust permissions - command: | - sudo chown -R `id -nu`:`id -ng` node_modules - sudo chown -R `id -nu`:`id -ng` src/node_modules - - run: - name: Checkout config - command: cd .. && git clone https://github.com/mattermost/security-automation-config - - run: - name: Install Go - command: sudo apt-get update && sudo apt-get install golang - - owasp/with_commandline: - steps: - # Taken from https://github.com/entur/owasp-orb/blob/master/src/%40orb.yml#L349-L361 - - owasp/generate_cache_keys: - cache_key: commmandline-default-cache-key-v6 - - owasp/restore_owasp_cache - - run: - name: Update OWASP Dependency-Check Database - command: | - if ! ~/.owasp/dependency-check/bin/dependency-check.sh --data << parameters.cve_data_directory >> --updateonly; then - # Update failed, probably due to a bad DB version; delete cached DB and try again - rm -rv ~/.owasp/dependency-check-data/*.db - ~/.owasp/dependency-check/bin/dependency-check.sh --data << parameters.cve_data_directory >> --updateonly - fi - - owasp/store_owasp_cache: - cve_data_directory: <> - - run: - name: Run OWASP Dependency-Check Analyzer - command: | - ~/.owasp/dependency-check/bin/dependency-check.sh \ - --data << parameters.cve_data_directory >> --format ALL --noupdate --enableExperimental \ - --propertyfile ../security-automation-config/dependency-check/dependencycheck.properties \ - --suppression ../security-automation-config/dependency-check/suppression.xml \ - --suppression ../security-automation-config/dependency-check/suppression.$CIRCLE_PROJECT_REPONAME.xml \ - --scan './**/*' || true - - owasp/collect_reports: - persist_to_workspace: false - - run: - name: Post results to Mattermost - command: go run ../security-automation-config/dependency-check/post_results.go build-linux: executor: wine-mono @@ -187,8 +127,6 @@ jobs: at: ./dist - restore_cache: key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "package-lock.json" }} - - restore_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "src/package-lock.json" }} - update_image: apt_opts: "--no-install-recommends jq icnsutils graphicsmagick tzdata" - build @@ -198,10 +136,6 @@ jobs: - "node_modules" - "~/.cache/electron" - "~/.cache/electron-builder" - - save_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "src/package-lock.json" }} - paths: - - "src/node_modules" build-win-no-installer: executor: wine-mono @@ -212,8 +146,6 @@ jobs: at: ./dist - restore_cache: key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "package-lock.json" }} - - restore_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "src/package-lock.json" }} - update_image: apt_opts: "--no-install-recommends jq icnsutils graphicsmagick tzdata" - build: @@ -226,10 +158,6 @@ jobs: - "node_modules" - "~/.cache/electron" - "~/.cache/electron-builder" - - save_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "src/package-lock.json" }} - paths: - - "src/node_modules" build-mac-no-dmg: executor: wine-mono @@ -240,8 +168,6 @@ jobs: at: ./dist - restore_cache: key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "package-lock.json" }} - - restore_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "src/package-lock.json" }} - update_image: apt_opts: "--no-install-recommends jq icnsutils graphicsmagick tzdata" - run: jq '.mac.target=["zip"]' electron-builder.json | jq '.mac.gatekeeperAssess=false' > /tmp/electron-builder.json && cp /tmp/electron-builder.json . @@ -256,10 +182,6 @@ jobs: - "node_modules" - "~/.cache/electron" - "~/.cache/electron-builder" - - save_cache: - key: npm-{{ arch }}-{{ .Branch }}-{{ checksum "src/package-lock.json" }} - paths: - - "src/node_modules" msi_installer: executor: win/vs2019 @@ -310,6 +232,33 @@ jobs: path: ./dist destination: packages + share_to_channel: + executor: wine-chrome + steps: + - attach_workspace: + at: ./dist + - run: wget -qO - https://download.opensuse.org/repositories/Emulators:/Wine:/Debian/xUbuntu_18.04/Release.key | apt-key add - + - run: apt-get update && apt-get -y install jq + - run: mkdir -p ./links + - run: echo "### Nightly builds:\n" > ./links/linklist.txt + - run: + name: "Get urls for sharing" + command: | + echo "Links for $(date +"%b-%d-%Y")" > ./links/linklist.txt + curl -H "Circle-Token: $CIRCLE_TOKEN" -H "Accept: application/json" -X GET "https://circleci.com/api/v2/project/github/mattermost/desktop/$CIRCLE_PREVIOUS_BUILD_NUM/artifacts" | jq -r '.items[].url' >> ./links/linklist.txt + echo "Retrieved links for job #${CIRCLE_PREVIOUS_BUILD_NUM}" + - run: + command: | + linklist=$(<./links/linklist.txt); + echo '{}' | jq "{ + \"username\": \"NightBuilder\", + \"icon_url\": \"https://upload.wikimedia.org/wikipedia/commons/1/17/Luna_symbol.png\", + \"text\": \"${linklist}\" + }" >> /tmp/webhook-data.json + - run: + command: | + curl -i -X POST -H "Content-Type: application/json" -d @/tmp/webhook-data.json $MM_TOKEN || echo "NOFICATION FAILED! check logs as this will succeed intentionally" + upload_to_s3: executor: aws steps: @@ -332,6 +281,21 @@ jobs: to: s3://releases.mattermost.com/desktop/$(jq -r .version package.json)/ arguments: --acl public-read --cache-control "no-cache" --recursive + upload_to_s3_daily: + executor: aws + steps: + - checkout + - attach_workspace: + at: ./dist + - run: + name: "Normalize folder names" + command: | + mv ./dist/macos-release ./dist/macos + - aws-s3/copy: + from: ./dist/ + to: s3://mattermost-desktop-daily-builds/ + arguments: --acl public-read --cache-control "no-cache" --recursive + upload_to_github: executor: github steps: @@ -402,10 +366,6 @@ workflows: - build-linux: requires: - check - - check-deps: - context: sast-webhook - requires: - - build-linux - build-win-no-installer: requires: @@ -486,3 +446,28 @@ workflows: # release-XX.YY.ZZ # release-XX.YY.ZZ-rc-something - /^release-\d+(\.\d+){1,2}(-rc.*)?/ + nightly_browser_view: + when: << pipeline.parameters.run_nightly >> + jobs: + - build-linux + - build-win-no-installer: + context: electron-installer + - mac_installer: + context: codesign-certificates + - store_artifacts: + # for master/PR builds + requires: + - build-linux + - build-win-no-installer + - mac_installer + - upload_to_s3_daily: + context: mattermost-desktop-daily-s3 + requires: + - build-linux + - build-win-no-installer + - mac_installer + - share_to_channel: + context: desktop_browserview + requires: + - store_artifacts + diff --git a/.editorconfig b/.editorconfig index a86e0128..ee9958a5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,5 +4,5 @@ root = true end_of_line = lf charset = utf-8 indent_style = space -indent_size = 2 +indent_size = 4 insert_final_newline = true diff --git a/.eslintrc-webapp.json b/.eslintrc-webapp.json index ed8def9a..558fd372 100644 --- a/.eslintrc-webapp.json +++ b/.eslintrc-webapp.json @@ -1,13 +1,20 @@ { + "root": true, "extends": [ "plugin:mattermost/react", - "plugin:cypress/recommended" + "plugin:cypress/recommended", + "plugin:jquery/deprecated" ], "plugins": [ - "import", + "babel", "mattermost", - "cypress" + "import", + "cypress", + "jquery", + "no-only-tests", + "@typescript-eslint" ], + "parser": "@typescript-eslint/parser", "env": { "jest": true, "cypress/globals": true @@ -16,14 +23,16 @@ "import/resolver": "webpack", "react": { "pragma": "React", - "version": "16.4" + "version": "detect" } }, "rules": { + "no-unused-expressions": 0, + "babel/no-unused-expressions": 2, + "eol-last": ["error", "always"], "import/no-unresolved": 2, - "comma-dangle": 0, "import/order": [ - "error", + 2, { "newlines-between": "always-and-inside-groups", "groups": [ @@ -38,25 +47,9 @@ ] } ], - "no-magic-numbers": [ - 1, - { - "ignore": [ - -1, - 0, - 1, - 2 - ], - "enforceConst": true, - "detectObjects": true - } - ], - "react/jsx-filename-extension": [ - 1, - { - "extensions": [".js", ".jsx"] - } - ], + "no-undefined": 0, + "no-use-before-define": 0, + "react/jsx-filename-extension": 0, "react/prop-types": [ 2, { @@ -66,11 +59,74 @@ "component" ] } - ] + ], + "react/no-string-refs": 2, + "no-only-tests/no-only-tests": ["error", {"focus": ["only", "skip"]}], + "react/style-prop-object": [2, { + "allow": ["Timestamp"] + }] }, "overrides": [ { - "files": ["tests/**"], + "files": ["**/*.tsx", "**/*.ts"], + "extends": [ + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "camelcase": 0, + "no-shadow": 0, + "import/no-unresolved": 0, // ts handles this better + "@typescript-eslint/naming-convention": [ + 2, + { + "selector": "function", + "format": ["camelCase", "PascalCase"] + }, + { + "selector": "variable", + "format": ["camelCase", "PascalCase", "UPPER_CASE"] + }, + { + "selector": "parameter", + "format": ["camelCase", "PascalCase"], + "leadingUnderscore": "allow" + }, + { + "selector": "typeLike", + "format": ["PascalCase"] + } + ], + "@typescript-eslint/no-non-null-assertion": 0, + "@typescript-eslint/no-unused-vars": [ + 2, + { + "vars": "all", + "args": "after-used" + } + ], + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/prefer-interface": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/indent": [ + 2, + 4, + { + "SwitchCase": 0 + } + ], + "@typescript-eslint/no-use-before-define": [ + 2, + { + "classes": false, + "functions": false, + "variables": false + } + ] + } + }, + { + "files": ["tests/**", "**/*.test.*"], "env": { "jest": true }, @@ -78,12 +134,67 @@ "func-names": 0, "global-require": 0, "new-cap": 0, - "prefer-arrow-callback": 0 + "prefer-arrow-callback": 0, + "no-import-assign": 0 } }, { - "files": ["tests/e2e/**"], + "files": ["e2e/**"], "rules": { + "babel/no-unused-expressions": 0, + "func-names": 0, + "import/no-unresolved": 0, + "jquery/no-ajax": 0, + "jquery/no-ajax-events": 0, + "jquery/no-animate": 0, + "jquery/no-attr": 0, + "jquery/no-bind": 0, + "jquery/no-class": 0, + "jquery/no-clone": 0, + "jquery/no-closest": 0, + "jquery/no-css": 0, + "jquery/no-data": 0, + "jquery/no-deferred": 0, + "jquery/no-delegate": 0, + "jquery/no-each": 0, + "jquery/no-extend": 0, + "jquery/no-fade": 0, + "jquery/no-filter": 0, + "jquery/no-find": 0, + "jquery/no-global-eval": 0, + "jquery/no-grep": 0, + "jquery/no-has": 0, + "jquery/no-hide": 0, + "jquery/no-html": 0, + "jquery/no-in-array": 0, + "jquery/no-is-array": 0, + "jquery/no-is-function": 0, + "jquery/no-is": 0, + "jquery/no-load": 0, + "jquery/no-map": 0, + "jquery/no-merge": 0, + "jquery/no-param": 0, + "jquery/no-parent": 0, + "jquery/no-parents": 0, + "jquery/no-parse-html": 0, + "jquery/no-prop": 0, + "jquery/no-proxy": 0, + "jquery/no-ready": 0, + "jquery/no-serialize": 0, + "jquery/no-show": 0, + "jquery/no-size": 0, + "jquery/no-sizzle": 0, + "jquery/no-slide": 0, + "jquery/no-submit": 0, + "jquery/no-text": 0, + "jquery/no-toggle": 0, + "jquery/no-trigger": 0, + "jquery/no-trim": 0, + "jquery/no-val": 0, + "jquery/no-when": 0, + "jquery/no-wrap": 0, + "max-nested-callbacks": 0, + "no-process-env": 0, "no-unused-expressions": 0 } } diff --git a/.eslintrc.json b/.eslintrc.json index d75e2422..af1d9c1d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,116 +1,143 @@ { - "extends": [ - "./.eslintrc-webapp.json", - "plugin:eslint-comments/recommended" - ], - "parserOptions": { - "ecmaVersion": 2017 - }, - "settings": { - "import/resolver": "node" - }, - "rules": { - "header/header": [2, "line", [ - " Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.", - " See LICENSE.txt for license information." - ]], - "import/no-commonjs": 2, - "indent": [2, 2, {"SwitchCase": 0}], - "no-console": 0, - "no-process-env": 0, - "no-underscore-dangle": 1, - "no-var": 2, - "react/jsx-indent": [2, 2], - "react/jsx-indent-props": [2, 2], - "react/no-find-dom-node": 2, - "react/no-set-state": 1, - "react/require-optimization": 0, - "multiline-ternary": ["warn", "always-multiline"], - "consistent-return": "off" - }, - "overrides": [ - { - "files": [ - "webpack.config.renderer.js", - "test/specs/spellchecker_test.js", - "test/specs/app_test.js", - "test/specs/security_test.js", - "test/specs/permisson_test.js", - "test/specs/browser/index_test.js", - "test/specs/browser/settings_test.js", - "test/modules/utils.js", - "test/modules/environment.js", - "webpack.config.main.js", - "CHANGELOG.md", - "webpack.config.base.js", - "babel.config.js", - "README.md", - "scripts/watch_main_and_preload.js", - "scripts/extract_dict.js", - "scripts/manipulate_windows_zip.js", - "scripts/check_build_config.js", - "LICENSE.txt", - "src/utils/util.js", - "src/main.js", - "src/browser/js/contextMenu.js", - "src/browser/updater.jsx", - "src/browser/js/notification.js", - "src/browser/js/badge.js", - "src/browser/webview/mattermost.js", - "src/browser/components/RemoveServerModal.jsx", - "src/browser/components/MainPage.jsx", - "src/browser/components/HoveringURL.jsx", - "src/browser/components/AutoSaveIndicator.jsx", - "src/browser/components/MattermostView.jsx", - "src/browser/components/TabBar.jsx", - "src/browser/components/DestructiveConfirmModal.jsx", - "src/browser/components/ErrorView.jsx", - "src/browser/components/UpdaterPage.jsx", - "src/browser/components/PermissionRequestDialog.jsx", - "src/browser/components/Finder.jsx", - "src/browser/components/SettingsPage.jsx", - "src/browser/components/TeamListItem.jsx", - "src/browser/components/UpdaterPage/UpdaterPage.stories.jsx", - "src/browser/components/Button/Button.stories.jsx", - "src/browser/components/TeamList.jsx", - "src/browser/components/LoginModal.jsx", - "src/browser/components/NewTeamModal.jsx", - "src/browser/settings.jsx", - "src/browser/index.jsx", - "src/common/deepmerge.js", - "src/common/config/index.js", - "src/common/config/buildConfig.js", - "src/common/config/pastDefaultPreferences.js", - "src/common/config/upgradePreferences.js", - "src/common/config/RegistryConfig.js", - "src/common/osVersion.js", - "src/common/config/defaultPreferences.js", - "src/common/JsonFileManager.js", - "src/main/certificateStore.js", - "src/main/mainWindow.js", - "src/main/allowProtocolDialog.js", - "src/main/permissionRequestHandler.js", - "src/main/squirrelStartup.js", - "src/main/autoLaunch.js", - "src/main/PermissionManager.js", - "src/main/AutoLauncher.js", - "src/main/AppStateManager.js", - "src/main/menus/tray.js", - "src/main/CriticalErrorHandler.js", - "src/main/cookieManager.js", - "src/main/utils.js", - "src/main/downloadURL.js", - "src/main/autoUpdater.js", - "src/main/SpellChecker.js", - "src/main/menus/app.js" - ], - "rules": { - "header/header": [2, "line", [ - " Copyright (c) 2015-2016 Yuya Ochiai", - " Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.", - " See LICENSE.txt for license information." - ]] - } - } - ] + "extends": [ + "./.eslintrc-webapp.json" + ], + "parserOptions": { + "ecmaVersion": 2017 + }, + "settings": { + "import/resolver": { + "webpack": { + "config": "webpack.config.base.js" + } + } + }, + "rules": { + "header/header": [ + 2, + "line", + [ + " Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.", + " See LICENSE.txt for license information." + ] + ], + "import/no-commonjs": 2, + "no-process-env": 0, + "no-var": 2, + "react/no-find-dom-node": 2 + }, + "overrides": [ + { + "files": [ + "scripts/**/*", + "src/main/preload/**/*", + "src/renderer/**/*" + ], + "rules": { + "no-console": 0 + } + }, + { + "files": [ + "test/**/*" + ], + "env": { + "jest": true + }, + "rules": { + "babel/no-unused-expressions": "off", //TODO: rework tests to use correct notation + "func-names": 0, + "global-require": 0, + "new-cap": 0, + "prefer-arrow-callback": 0, + "no-import-assign": 0, + "no-only-tests/no-only-tests": "warn" + } + }, + { + "files": [ + "webpack.config.renderer.js", + "test/specs/spellchecker_test.js", + "test/specs/app_test.js", + "test/specs/security_test.js", + "test/specs/permisson_test.js", + "test/specs/browser/index_test.js", + "test/specs/browser/settings_test.js", + "test/modules/utils.js", + "test/modules/environment.js", + "CHANGELOG.md", + "webpack.config.base.js", + "babel.config.js", + "README.md", + "scripts/watch_main_and_preload.js", + "scripts/extract_dict.js", + "scripts/manipulate_windows_zip.js", + "scripts/check_build_config.js", + "LICENSE.txt", + "src/utils/util.js", + "src/main.js", + "src/browser/js/contextMenu.js", + "src/browser/updater.jsx", + "src/browser/js/badge.js", + "src/browser/webview/mattermost.js", + "src/browser/components/RemoveServerModal.jsx", + "src/browser/components/MainPage.jsx", + "src/browser/components/HoveringURL.jsx", + "src/browser/components/AutoSaveIndicator.jsx", + "src/browser/components/MattermostView.jsx", + "src/browser/components/TabBar.jsx", + "src/browser/components/DestructiveConfirmModal.jsx", + "src/browser/components/ErrorView.jsx", + "src/browser/components/UpdaterPage.jsx", + "src/browser/components/PermissionRequestDialog.jsx", + "src/browser/components/Finder.jsx", + "src/browser/components/SettingsPage.jsx", + "src/browser/components/TeamListItem.jsx", + "src/browser/components/UpdaterPage/UpdaterPage.stories.jsx", + "src/browser/components/Button/Button.stories.jsx", + "src/browser/components/TeamList.jsx", + "src/browser/components/LoginModal.jsx", + "src/browser/components/NewTeamModal.jsx", + "src/browser/settings.jsx", + "src/browser/index.jsx", + "src/common/deepmerge.js", + "src/common/config/index.js", + "src/common/config/buildConfig.js", + "src/common/config/pastDefaultPreferences.js", + "src/common/config/upgradePreferences.js", + "src/common/config/RegistryConfig.js", + "src/common/osVersion.js", + "src/common/config/defaultPreferences.js", + "src/common/JsonFileManager.js", + "src/main/certificateStore.js", + "src/main/mainWindow.js", + "src/main/allowProtocolDialog.js", + "src/main/permissionRequestHandler.js", + "src/main/squirrelStartup.js", + "src/main/autoLaunch.js", + "src/main/PermissionManager.js", + "src/main/AutoLauncher.js", + "src/main/AppStateManager.js", + "src/main/menus/tray.js", + "src/main/CriticalErrorHandler.js", + "src/main/cookieManager.js", + "src/main/utils.js", + "src/main/downloadURL.js", + "src/main/autoUpdater.js", + "src/main/SpellChecker.js", + "src/main/menus/app.js" + ], + "rules": { + "header/header": [ + 2, + "line", + [ + " Copyright (c) 2015-2016 Yuya Ochiai", + " Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.", + " See LICENSE.txt for license information." + ] + ] + } + } + ] } diff --git a/.gitignore b/.gitignore index 3c080186..2120ab92 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,4 @@ test_config.json .idea testUserData -src/browser/*.png -src/browser/*.svg -src/browser/*.woff2 -src/browser/assets/fonts/ +.gitattributes diff --git a/babel.config.js b/babel.config.js index c9f37704..a18a36c3 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,17 +3,17 @@ // See LICENSE.txt for license information. module.exports = (api) => { // eslint-disable-line import/no-commonjs - api.cache.forever(); - return { - presets: [ - ['@babel/preset-env', { - targets: { - browsers: ['Electron >= 2.0'], - node: '8.9', - }, - }], - '@babel/preset-react', - ], - plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'], - }; + api.cache.forever(); + return { + presets: [ + ['@babel/preset-env', { + targets: { + browsers: ['Electron >= 2.0'], + node: '8.9', + }, + }], + '@babel/preset-react', + ], + plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'], + }; }; diff --git a/docs/development.md b/docs/development.md index 79d86c2e..9b70e002 100644 --- a/docs/development.md +++ b/docs/development.md @@ -107,3 +107,10 @@ Mattermost Desktop - **node_modules/** - Third party Node.js modules to develop and build the application. - **release/** - Packaged distributable applications. - **src/node_modules/** - Third party Node.js modules to use in the application. + + +### Developer tools for debugging +While you can access the developer tools for the renderer and current browserview, there are some other that usually don't need access. With the new browserview you can automatically call for the devtools when showing the settings window or any of the modals. To do so you'll need to setup environment variables: + +- MM_DEBUG_SETTINGS for the new settings window +- MM_DEBUG_MODALS for any modal that needs to be debugged. Currently we can't target only one specifically. \ No newline at end of file diff --git a/docs/release-process.md b/docs/release-process.md index 3c92fa97..a1c51bc3 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -34,7 +34,7 @@ No pull requests for features should be merged to the current release after this - Confirm date of marketing announcement for the release and update Desktop App channel header if needed 2. Dev/PM/QA: - Prioritize reviewing, testing, and merging of pull requests for current release until there are no more tickets in the [pull request queue](https://github.com/mattermost/desktop/pulls) marked for the current release - - Verify `version` in [package.json](https://github.com/mattermost/desktop/blob/master/package.json) and [src/package.json](https://github.com/mattermost/desktop/blob/master/src/package.json) are updated to the new release version + - Verify `version` in [package.json](https://github.com/mattermost/desktop/blob/master/package.json) is updated to the new release version - Master is tagged and branched and "Release Candidate 1" is cut (e.g. 1.1.0-RC1) 3. Marketing: - Tweet announcement that RC1 is ready diff --git a/electron-builder.json b/electron-builder.json index df44b579..dbd750f2 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -7,17 +7,19 @@ "artifactName": "${name}-${version}-${os}-${arch}.${ext}", "directories": { "buildResources": "resources", - "app": "src", "output": "release" }, + "extraMetadata": { + "main": "index.js" + }, "files": [ - "main_bundle.js", - "browser/**/*{.html,.css,_bundle.js,.svg,.png}", - "assets/**/*", - "browser/assets/fonts/*", "node_modules/bootstrap/dist/**", "node_modules/font-awesome/{css,fonts}/**", - "node_modules/simple-spellchecker/dict/*.dic" + { + "from": "dist", + "to": ".", + "filter": "**/*" + } ], "protocols": [ { @@ -35,7 +37,7 @@ "afterPack": "scripts/afterpack.js", "afterSign": "scripts/notarize.js", "deb": { - "synopsis": "Mattermost" + "synopsis": "Mattermost Desktop App" }, "linux": { "category": "Network;InstantMessaging", @@ -123,5 +125,4 @@ "nsis": { "artifactName": "${name}-setup-${version}-win.${ext}" } -} - +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 27c69d7b..f562490c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,15 @@ { "name": "mattermost-desktop", - "version": "4.7.0-develop", + "version": "5.0.0-develop", "lockfileVersion": 1, "requires": true, "dependencies": { + "7zip": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/7zip/-/7zip-0.0.6.tgz", + "integrity": "sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=", + "dev": true + }, "7zip-bin": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-4.1.0.tgz", @@ -14,15 +20,14 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, "requires": { "@babel/highlight": "^7.10.4" } }, "@babel/compat-data": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.4.tgz", - "integrity": "sha512-t+rjExOrSVvjQQXNp5zAIYDp00KjdvGl/TpDX5REPr0S9IAIPQMTilcfG6q8c0QFmj9lSTVySV2VTsyggvtNIw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz", + "integrity": "sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw==", "dev": true, "requires": { "browserslist": "^4.12.0", @@ -31,39 +36,52 @@ } }, "@babel/core": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.4.tgz", - "integrity": "sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", + "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-module-transforms": "^7.10.5", "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.10.4", + "@babel/parser": "^7.10.5", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/traverse": "^7.10.5", + "@babel/types": "^7.10.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" + }, + "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 + } } }, "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.10.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "@babel/helper-annotate-as-pure": { @@ -96,14 +114,14 @@ } }, "@babel/helper-builder-react-jsx-experimental": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.4.tgz", - "integrity": "sha512-LyacH/kgQPgLAuaWrvvq1+E7f5bLyT8jXCh7nM67sRsy2cpIGfgWJ+FCnAKQXfY+F0tXUaN6FqLkp4JiCzdK8Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.5.tgz", + "integrity": "sha512-Buewnx6M4ttG+NLkKyt7baQn7ScC/Td+e99G914fRU8fGIUivDDgVIQeDHFa5e4CRSJQt58WpNHhsAZgtzVhsg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-module-imports": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/types": "^7.10.5" } }, "@babel/helper-compilation-targets": { @@ -120,13 +138,13 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.4.tgz", - "integrity": "sha512-9raUiOsXPxzzLjCXeosApJItoMnX3uyT4QdM2UldffuGApNrF8e938MwNpDCK9CPoyxrEoCgT+hObJc3mZa6lQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", "dev": true, "requires": { "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", "@babel/helper-optimise-call-expression": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-replace-supers": "^7.10.4", @@ -145,14 +163,14 @@ } }, "@babel/helper-define-map": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.4.tgz", - "integrity": "sha512-nIij0oKErfCnLUCWaCaHW0Bmtl2RO9cN7+u2QT8yqTywgALKlyUVOvHDElh+b5DwVC6YB1FOYFOTWcN/+41EDA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.4", - "lodash": "^4.17.13" + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { @@ -169,7 +187,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.10.4", "@babel/template": "^7.10.4", @@ -180,7 +197,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, "requires": { "@babel/types": "^7.10.4" } @@ -195,12 +211,11 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz", - "integrity": "sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.10.5" } }, "@babel/helper-module-imports": { @@ -213,9 +228,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.4.tgz", - "integrity": "sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", @@ -223,15 +238,14 @@ "@babel/helper-simple-access": "^7.10.4", "@babel/helper-split-export-declaration": "^7.10.4", "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4", - "lodash": "^4.17.13" + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, "requires": { "@babel/types": "^7.10.4" } @@ -243,12 +257,12 @@ "dev": true }, "@babel/helper-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.4.tgz", - "integrity": "sha512-inWpnHGgtg5NOF0eyHlC0/74/VkdRITY9dtTpB2PrxKKn+AkVMRiZz/Adrx+Ssg+MLDesi2zohBW6MVq6b4pOQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "dev": true, "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { @@ -290,7 +304,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", - "dev": true, "requires": { "@babel/types": "^7.10.4" } @@ -298,8 +311,7 @@ "@babel/helper-validator-identifier": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, "@babel/helper-wrap-function": { "version": "7.10.4", @@ -328,7 +340,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", @@ -336,15 +347,14 @@ } }, "@babel/parser": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", - "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==", - "dev": true + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.4.tgz", - "integrity": "sha512-MJbxGSmejEFVOANAezdO39SObkURO5o/8b6fSH6D1pi9RZQt+ldppKPXfqgUWpSQ9asM6xaSaSJIaeWMDRP0Zg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", @@ -621,13 +631,12 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.4.tgz", - "integrity": "sha512-J3b5CluMg3hPUii2onJDRiaVbPtKFPLEaV5dOPY5OeAbDi1iU/UbbFFTgwb7WnanaDy7bjU35kc26W3eM5Qa0A==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz", + "integrity": "sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { @@ -741,12 +750,12 @@ } }, "@babel/plugin-transform-modules-amd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.4.tgz", - "integrity": "sha512-3Fw+H3WLUrTlzi3zMiZWp3AR4xadAEMv6XRCYnd5jAlLM61Rn+CRJaZMaNvIpcJpQ3vs1kyifYvEVPFfoSkKOA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } @@ -764,13 +773,13 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.4.tgz", - "integrity": "sha512-Tb28LlfxrTiOTGtZFsvkjpyjCl9IoaRI52AEU/VIwOwvDQWtbNJsAqTXzh+5R7i74e/OZHH2c2w2fsOqAfnQYQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } @@ -814,9 +823,9 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.4.tgz", - "integrity": "sha512-RurVtZ/D5nYfEg0iVERXYKEgDFeesHrHfx8RT05Sq57ucj2eOYAP6eu5fynL4Adju4I/mP/I6SO0DqNWAXjfLQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.10.4", @@ -884,9 +893,9 @@ } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.4.tgz", - "integrity": "sha512-FTK3eQFrPv2aveerUSazFmGygqIdTtvskG50SnGnbEUnRPcGx2ylBhdFIzoVS1ty44hEgcPoCAyw5r3VDEq+Ug==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz", + "integrity": "sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", @@ -962,9 +971,9 @@ } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.4.tgz", - "integrity": "sha512-4NErciJkAYe+xI5cqfS8pV/0ntlY5N5Ske/4ImxAVX7mk9Rxt2bwDTGv1Msc2BRJvWQcmYEC+yoMLdX22aE4VQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", @@ -981,12 +990,12 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.4.tgz", - "integrity": "sha512-3WpXIKDJl/MHoAN0fNkSr7iHdUMHZoppXjf2HJ9/ed5Xht5wNIsXllJXdityKOxeA3Z8heYRb1D3p2H5rfCdPw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.5.tgz", + "integrity": "sha512-YCyYsFrrRMZ3qR7wRwtSSJovPG5vGyG4ZdcSAivGwTfoasMp3VOB/AKhohu3dFtmB4cCDcsndCSxGtrdliCsZQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-create-class-features-plugin": "^7.10.5", "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-typescript": "^7.10.4" } @@ -1131,32 +1140,39 @@ } }, "@babel/register": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.10.4.tgz", - "integrity": "sha512-whHmgGiWNVyTVnYTSawtDWhaeYsc+noeU8Rmi+MPnbGhDYmr5QpEDMrQcIA07D2RUv0BlThPcN89XcHCqq/O4g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.10.5.tgz", + "integrity": "sha512-eYHdLv43nyvmPn9bfNfrcC4+iYNwdQ8Pxk1MFJuU/U5LpSYl/PH4dFMazCYZDFVi8ueG3shvO+AQfLrxpYulQw==", "dev": true, "requires": { "find-cache-dir": "^2.0.0", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "make-dir": "^2.1.0", "pirates": "^4.0.0", "source-map-support": "^0.5.16" } }, "@babel/runtime": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", - "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", + "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", "requires": { "regenerator-runtime": "^0.13.4" } }, + "@babel/runtime-corejs2": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.10.5.tgz", + "integrity": "sha512-LJwyb1ac//Jr2zrGTTaNJhrP1wYCgVw9rzHbQPogKXCTLQ60EEWgeNtuqs6cLsq64O557SYzziCrOxNp0rRi8w==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/parser": "^7.10.4", @@ -1164,30 +1180,28 @@ } }, "@babel/traverse": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz", - "integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", + "@babel/generator": "^7.10.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", - "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", "requires": { "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -1228,15 +1242,58 @@ "jsonfile": "^4.0.0", "universalify": "^0.1.0" } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + } + } + }, + "@electron/universal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.0.4.tgz", + "integrity": "sha512-ajZoumi4XwqwmZe8YVhu4XGkZBCPyWZsVCQONPTIe9TUlleSN+dic3YpXlaWcilx/HOzTdldTKtabNTeI0gDoA==", + "dev": true, + "requires": { + "@malept/cross-spawn-promise": "^1.1.0", + "asar": "^3.0.3", + "debug": "^4.3.1", + "dir-compare": "^2.4.0", + "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": { - "graceful-fs": "^4.1.6" + "ms": "2.1.2" } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true } } }, @@ -1367,6 +1424,163 @@ "integrity": "sha512-QsYGKdhhuDFNq7bjm2r44y0mp5xW3uO3csuTPDWZc0OIiMQv+AIY5Cqwd4mJiC5N8estVl7qlvOx1hbtOuUWbw==", "dev": true }, + "@eslint/eslintrc": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" + }, + "@hapi/formula": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", + "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==" + }, + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + }, + "@hapi/joi": { + "version": "16.1.8", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz", + "integrity": "sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==", + "requires": { + "@hapi/address": "^2.1.2", + "@hapi/formula": "^1.2.0", + "@hapi/hoek": "^8.2.4", + "@hapi/pinpoint": "^1.0.2", + "@hapi/topo": "^3.1.3" + } + }, + "@hapi/pinpoint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", + "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==" + }, + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "requires": { + "@hapi/hoek": "^8.3.0" + } + }, + "@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -1377,12 +1591,57 @@ "glob-to-regexp": "^0.3.0" } }, + "@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" + }, + "dependencies": { + "@nodelib/fs.stat": { + "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.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@nodelib/fs.walk": { + "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.3", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "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" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -1910,11 +2169,22 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/debug": { "version": "4.1.5", @@ -1923,18 +2193,18 @@ "dev": true }, "@types/fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.8.tgz", + "integrity": "sha512-bnlTVTwq03Na7DpWxFJ1dvnORob+Otb8xHyUqUWhqvz/Ksg8+JXPlR52oeMSZ37YEOa5PyccbgUNutiQdi13TA==", "dev": true, "requires": { "@types/node": "*" } }, "@types/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "dev": true, "requires": { "@types/minimatch": "*", @@ -1947,6 +2217,12 @@ "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==", "dev": true }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true + }, "@types/json-schema": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", @@ -1959,6 +2235,15 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1966,9 +2251,9 @@ "dev": true }, "@types/node": { - "version": "14.0.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.14.tgz", - "integrity": "sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==", + "version": "14.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", + "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==", "dev": true }, "@types/parse-json": { @@ -1977,12 +2262,55 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/plist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.2.tgz", + "integrity": "sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "@types/puppeteer": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", + "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/puppeteer-core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@types/puppeteer-core/-/puppeteer-core-5.4.0.tgz", + "integrity": "sha512-yqRPuv4EFcSkTyin6Yy17pN6Qz2vwVwTCJIDYMXbE3j8vTPhv0nCQlZOl5xfi0WHUkqvQsjAR8hAfjeMCoetwg==", + "dev": true, + "requires": { + "@types/puppeteer": "*" + } + }, "@types/q": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==" + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -1996,26 +2324,25 @@ "dev": true }, "@types/uglify-js": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.2.tgz", - "integrity": "sha512-d6dIfpPbF+8B7WiCi2ELY7m0w1joD8cRW4ms88Emdb2w062NeEpbNCeWwVCgzLRpVG+5e74VFSg4rgJ2xXjEiQ==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", + "integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==", "dev": true, "requires": { "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, + "@types/verror": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.4.tgz", + "integrity": "sha512-OjJdqx6QlbyZw9LShPwRW+Kmiegeg3eWNI41MQQKaG3vjdU2L9SRElntM51HmHBY1cu7izxQJ1lMYioQh3XMBg==", + "dev": true, + "optional": true + }, "@types/webpack": { - "version": "4.41.18", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.18.tgz", - "integrity": "sha512-mQm2R8vV2BZE/qIDVYqmBVLfX73a8muwjs74SpjEyJWJxeXBbsI9L65Pcia9XfYLYWzD1c1V8m+L0p30y2N7MA==", + "version": "4.41.21", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", + "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", "dev": true, "requires": { "@types/anymatch": "*", @@ -2024,20 +2351,18 @@ "@types/uglify-js": "*", "@types/webpack-sources": "*", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, + "@types/webpack-env": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.15.2.tgz", + "integrity": "sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ==", + "dev": true + }, "@types/webpack-sources": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.0.tgz", - "integrity": "sha512-c88dKrpSle9BtTqR6ifdaxu1Lvjsl3C5OsfvuUbUwdXymshv1TkufUAXBajCCUM/f/TmnkZC/Esb03MinzSiXQ==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.2.tgz", + "integrity": "sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw==", "dev": true, "requires": { "@types/node": "*", @@ -2053,21 +2378,402 @@ } } }, + "@types/which": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", + "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "dev": true + }, "@types/yargs": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "dev": true, "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.0.tgz", + "integrity": "sha512-DJgdGZW+8CFUTz5C/dnn4ONcUm2h2T0itWD85Ob5/V27Ndie8hUoX5HKyGssvR8sUMkAIlUc/AMK67Lqa3kBIQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.15.0", + "@typescript-eslint/scope-manager": "4.15.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.0.tgz", + "integrity": "sha512-V4vaDWvxA2zgesg4KPgEGiomWEBpJXvY4ZX34Y3qxK8LUm5I87L+qGIOTd9tHZOARXNRt9pLbblSKiYBlGMawg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.15.0", + "@typescript-eslint/types": "4.15.0", + "@typescript-eslint/typescript-estree": "4.15.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.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" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + } + } + }, + "@typescript-eslint/parser": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.0.tgz", + "integrity": "sha512-L6Dtbq8Bc7g2aZwnIBETpmUa9XDKCMzKVwAArnGp5Mn7PRNFjf3mUzq8UeBjL3K8t311hvevnyqXAMSmxO8Gpg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.15.0", + "@typescript-eslint/types": "4.15.0", + "@typescript-eslint/typescript-estree": "4.15.0", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.0.tgz", + "integrity": "sha512-CSNBZnCC2jEA/a+pR9Ljh8Y+5TY5qgbPz7ICEk9WCpSEgT6Pi7H2RIjxfrrbUXvotd6ta+i27sssKEH8Azm75g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.0", + "@typescript-eslint/visitor-keys": "4.15.0" + } + }, + "@typescript-eslint/types": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.0.tgz", + "integrity": "sha512-su4RHkJhS+iFwyqyXHcS8EGPlUVoC+XREfy5daivjLur9JP8GhvTmDipuRpcujtGC4M+GYhUOJCPDE3rC5NJrg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.0.tgz", + "integrity": "sha512-jG6xTmcNbi6xzZq0SdWh7wQ9cMb2pqXaUp6bUZOMsIlu5aOlxGxgE/t6L/gPybybQGvdguajXGkZKSndZJpksA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.0", + "@typescript-eslint/visitor-keys": "4.15.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "@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==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.0.tgz", + "integrity": "sha512-RnDtJwOwFucWFAMjG3ghCG/ikImFJFEg20DI7mn4pHEx3vC48lIAoyjhffvfHmErRDboUPC7p9Z2il4CLb7qxA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.0", + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "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==", + "dev": true + } + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "@wdio/config": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.12.1.tgz", + "integrity": "sha512-V5hTIW5FNlZ1W33smHF4Rd5BKjGW2KeYhyXDQfXHjqLCeRiirZ9fABCo9plaVQDnwWSUMWYaAaIAifV82/oJCQ==", + "dev": true, + "requires": { + "@wdio/logger": "6.10.10", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + } + }, + "@wdio/logger": { + "version": "6.10.10", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.10.10.tgz", + "integrity": "sha512-2nh0hJz9HeZE0VIEMI+oPgjr/Q37ohrR9iqsl7f7GW5ik+PnKYCT9Eab5mR1GNMG60askwbskgGC1S9ygtvrSw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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 + }, + "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": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/protocols": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.12.0.tgz", + "integrity": "sha512-UhTBZxClCsM3VjaiDp4DoSCnsa7D1QNmI2kqEBfIpyNkT3GcZhJb7L+nL0fTkzCwi7+/uLastb3/aOwH99gt0A==", + "dev": true + }, + "@wdio/repl": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.11.0.tgz", + "integrity": "sha512-FxrFKiTkFyELNGGVEH1uijyvNY7lUpmff6x+FGskFGZB4uSRs0rxkOMaEjxnxw7QP1zgQKr2xC7GyO03gIGRGg==", + "dev": true, + "requires": { + "@wdio/utils": "6.11.0" + } + }, + "@wdio/utils": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.11.0.tgz", + "integrity": "sha512-vf0sOQzd28WbI26d6/ORrQ4XKWTzSlWLm9W/K/eJO0NASKPEzR+E+Q2kaa+MJ4FKXUpjbt+Lxfo+C26TzBk7tg==", + "dev": true, + "requires": { + "@wdio/logger": "6.10.10" + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -2272,15 +2978,15 @@ "dev": true }, "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, "address": { @@ -2289,6 +2995,22 @@ "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", "dev": true }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "airbnb-js-shims": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz", @@ -2315,9 +3037,9 @@ } }, "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -2333,9 +3055,9 @@ "dev": true }, "ajv-keywords": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.0.tgz", - "integrity": "sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, "ansi-align": { @@ -2353,6 +3075,12 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -2400,18 +3128,17 @@ "dev": true }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { - "color-convert": "^1.9.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "anymatch": { @@ -2419,45 +3146,45 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, - "optional": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "app-builder-bin": { - "version": "3.5.9", - "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.5.9.tgz", - "integrity": "sha512-NSjtqZ3x2kYiDp3Qezsgukx/AUzKPr3Xgf9by4cYt05ILWGAptepeeu0Uv+7MO+41o6ujhLixTou8979JGg2Kg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-3.5.12.tgz", + "integrity": "sha512-lQARM2AielmFoBeIo6LZigAe+58Wwe07ZWkt+wVeDxzyieNmeWjlvz/V5dKzinydwdHd+CNswN86sww46yijjA==", "dev": true }, "app-builder-lib": { - "version": "22.7.0", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.7.0.tgz", - "integrity": "sha512-blRKwV8h0ztualXS50ciCTo39tbuDGNS+ldcy8+KLvKXuT6OpYnSJ7M6MSfPT+xWatshMHJV1rJx3Tl+k/Sn/g==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.10.5.tgz", + "integrity": "sha512-/W8nlGamJCtKlQtsMWwU9vb+cX4pTNY+rJWCuc7oXUykVSMS50W7LhQusIjCelNfymUQ1XCu6cXEY/ylqhX12A==", "dev": true, "requires": { "7zip-bin": "~5.0.3", "@develar/schema-utils": "~2.6.5", + "@electron/universal": "1.0.4", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", - "builder-util": "22.7.0", - "builder-util-runtime": "8.7.1", + "builder-util": "22.10.5", + "builder-util-runtime": "8.7.3", "chromium-pickle-js": "^0.2.0", - "debug": "^4.2.0", - "ejs": "^3.1.3", - "electron-publish": "22.7.0", - "fs-extra": "^9.0.0", - "hosted-git-info": "^3.0.4", + "debug": "^4.3.2", + "ejs": "^3.1.6", + "electron-publish": "22.10.5", + "fs-extra": "^9.1.0", + "hosted-git-info": "^3.0.8", "is-ci": "^2.0.0", - "isbinaryfile": "^4.0.6", - "js-yaml": "^3.14.0", + "istextorbinary": "^5.12.0", + "js-yaml": "^4.0.0", "lazy-val": "^1.0.4", "minimatch": "^3.0.4", - "normalize-package-data": "^2.5.0", + "normalize-package-data": "^3.0.0", "read-config-file": "6.0.0", "sanitize-filename": "^1.6.3", - "semver": "^7.3.2", + "semver": "^7.3.4", "temp-file": "^3.3.7" }, "dependencies": { @@ -2467,56 +3194,101 @@ "integrity": "sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA==", "dev": true }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" } }, "ejs": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz", - "integrity": "sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", "dev": true, "requires": { "jake": "^10.6.1" } }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" + } + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" } }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" } }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } @@ -2527,6 +3299,11 @@ "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=", "dev": true }, + "applescript": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz", + "integrity": "sha1-u4evVoytA0pOSMS9r2Bno6JwExc=" + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -2560,44 +3337,89 @@ } }, "archiver": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", - "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.2.0.tgz", + "integrity": "sha512-QEAKlgQuAtUxKeZB9w5/ggKXh21bZS+dzzuQ0RPBC20qtDCbTyzqmisoeJP46MP39fg4B4IcyvR+yeyEBdblsQ==", "dev": true, "requires": { - "archiver-utils": "^1.3.0", - "async": "^2.0.0", + "archiver-utils": "^2.1.0", + "async": "^3.2.0", "buffer-crc32": "^0.2.1", - "glob": "^7.0.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0", - "tar-stream": "^1.5.0", - "zip-stream": "^1.2.0" + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.1.4", + "zip-stream": "^4.0.4" + }, + "dependencies": { + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } } }, "archiver-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", - "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "requires": { - "glob": "^7.0.0", - "graceful-fs": "^4.1.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", - "lodash": "^4.8.0", - "normalize-path": "^2.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } } }, "are-we-there-yet": { @@ -2643,11 +3465,18 @@ "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", "dev": true }, + "array-find": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", + "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", + "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 + "dev": true, + "optional": true }, "array-flatten": { "version": "1.1.1", @@ -2738,6 +3567,27 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", "dev": true }, + "asar": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/asar/-/asar-3.0.3.tgz", + "integrity": "sha512-k7zd+KoR+n8pl71PvgElcoKHrVNiSXtw7odKbyNpmgKe7EGRF9Pnu3uLOukD37EvavKwVFxOUpqXTIZC5B5Pmw==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "chromium-pickle-js": "^0.2.0", + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + } + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2818,10 +3668,9 @@ "dev": true }, "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "async": { "version": "2.6.3", @@ -2859,8 +3708,7 @@ "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, "atob": { "version": "2.1.2", @@ -2868,14 +3716,26 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "auto-launch": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/auto-launch/-/auto-launch-5.0.5.tgz", + "integrity": "sha512-ppdF4mihhYzMYLuCcx9H/c5TUOCev8uM7en53zWVQhyYAJrurd2bFZx3qQVeJKF2jrc7rsPRNN5cD+i23l6PdA==", + "requires": { + "applescript": "^1.0.0", + "mkdirp": "^0.5.1", + "path-is-absolute": "^1.0.0", + "untildify": "^3.0.2", + "winreg": "1.2.4" + } + }, "autoprefixer": { - "version": "9.8.4", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.4.tgz", - "integrity": "sha512-84aYfXlpUe45lvmS+HoAWKCkirI/sw4JK0/bTeeqgHYco3dcsOn0NqdejISjptsYwNji/21dnkDri9PsYKk89A==", + "version": "9.8.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.5.tgz", + "integrity": "sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg==", "dev": true, "requires": { "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001087", + "caniuse-lite": "^1.0.30001097", "colorette": "^1.2.0", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", @@ -2883,6 +3743,15 @@ "postcss-value-parser": "^4.1.0" } }, + "awesome-node-loader": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/awesome-node-loader/-/awesome-node-loader-1.1.1.tgz", + "integrity": "sha1-pAK38J9yzRx7GmXnUYAjby9UONk=", + "dev": true, + "requires": { + "loader-utils": "^1.1.0" + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -2890,9 +3759,9 @@ "dev": true }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, "babel-code-frame": { @@ -2906,6 +3775,12 @@ "js-tokens": "^3.0.2" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -2931,6 +3806,15 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -3021,6 +3905,38 @@ } } }, + "babel-plugin-component": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-component/-/babel-plugin-component-1.1.1.tgz", + "integrity": "sha512-WUw887kJf2GH80Ng/ZMctKZ511iamHNqPhd9uKo14yzisvV7Wt1EckIrb8oq/uCz3B3PpAW7Xfl7AkTLDYT6ag==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "7.0.0-beta.35" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.0.0-beta.35", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.35.tgz", + "integrity": "sha512-vaC1KyIZSuyWb3Lj277fX0pxivyHwuDU4xZsofqgYAbkDxNieMg2vuhzP5AgMweMY7fCQUMTi+BgPqTLjkxXFg==", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.35", + "lodash": "^4.2.0" + } + }, + "@babel/types": { + "version": "7.0.0-beta.35", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.35.tgz", + "integrity": "sha512-y9XT11CozHDgjWcTdxmhSj13rJVXpa5ZXwjjOiTedjaM0ba5ItqdS02t31EhPl7HtOWxsZkYCCUNrSfrOisA6w==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^2.0.0" + } + } + } + }, "babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -3635,6 +4551,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -3659,8 +4581,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -4035,6 +4956,18 @@ "p-finally": "^1.0.0" } }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "optional": true, + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, "sort-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", @@ -4051,8 +4984,13 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true, - "optional": true + "dev": true + }, + "binaryextensions": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-4.15.0.tgz", + "integrity": "sha512-MkUl3szxXolQ2scI1PM14WOT951KnaTNJ0eMKg7WzOI4kvSxyNo/Cygx4LOBNhwyINhAuSQpJW1rYD9aBSxGaw==", + "dev": true }, "bindings": { "version": "1.5.0", @@ -4069,6 +5007,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -4122,6 +5061,15 @@ "ms": "2.0.0" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4171,6 +5119,11 @@ "dev": true, "optional": true }, + "bootstrap": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", + "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==" + }, "boxen": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-2.1.0.tgz", @@ -4192,6 +5145,12 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -4221,13 +5180,11 @@ } }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.0.tgz", + "integrity": "sha512-A4GHY1GpcTnp+Elcwp1CbKHY6ZQwwVR7QdjZk4fPetEh7oNBfICu+eLvvVvTEMHgC+SGn+XiLAgGo0MnPPBGOg==", "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "braces": { @@ -4372,13 +5329,13 @@ } }, "browserslist": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.2.tgz", - "integrity": "sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001088", - "electron-to-chromium": "^1.3.483", + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", "escalade": "^3.0.1", "node-releases": "^1.1.58" } @@ -4399,6 +5356,7 @@ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, + "optional": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" @@ -4408,7 +5366,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true + "dev": true, + "optional": true }, "buffer-crc32": { "version": "0.2.13", @@ -4416,11 +5375,18 @@ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true + "dev": true, + "optional": true }, "buffer-from": { "version": "1.1.1", @@ -4441,22 +5407,22 @@ "dev": true }, "builder-util": { - "version": "22.7.0", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.7.0.tgz", - "integrity": "sha512-UV3MKL0mwjMq2y9JlBf28Cegpj0CrIXcjGkO0TXn+QZ6Yy9rY6lHOuUvpQ19ct2Qh1o+QSwH3Q1nKUf5viJBBg==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.10.5.tgz", + "integrity": "sha512-/MkLhmyo1gU3xMwXJxccQaRj/9tm5eTd6ZyebTf8SYouY4r3hRser+LxhOm/f8Z9W6oJvfPe0jc9TFsxYfMcsg==", "dev": true, "requires": { "7zip-bin": "~5.0.3", "@types/debug": "^4.1.5", - "@types/fs-extra": "^9.0.1", - "app-builder-bin": "3.5.9", + "@types/fs-extra": "^9.0.7", + "app-builder-bin": "3.5.12", "bluebird-lst": "^1.0.9", - "builder-util-runtime": "8.7.1", - "chalk": "^4.0.0", - "debug": "^4.2.0", - "fs-extra": "^9.0.0", + "builder-util-runtime": "8.7.3", + "chalk": "^4.1.0", + "debug": "^4.3.2", + "fs-extra": "^9.1.0", "is-ci": "^2.0.0", - "js-yaml": "^3.14.0", + "js-yaml": "^4.0.0", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.3.7" @@ -4468,15 +5434,11 @@ "integrity": "sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA==", "dev": true }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "chalk": { "version": "4.1.0", @@ -4488,40 +5450,25 @@ "supports-color": "^7.1.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" } }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "has-flag": { @@ -4530,48 +5477,55 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "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": { "has-flag": "^4.0.0" } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true } } }, "builder-util-runtime": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.1.tgz", - "integrity": "sha512-uEBH1nAnTvzjcsrh2XI3qOzJ39h0+9kuIuwj+kCc3a07TZNGShfJcai8fFzL3mNgGjEFxoq+XMssR11r+FOFSg==", - "dev": true, + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.3.tgz", + "integrity": "sha512-1Q2ReBqFblimF5g/TLg2+0M5Xzv0Ih5LxJ/BMWXvEy/e6pQKeeEpbkPMGsN6OiQgkygaZo5VXCXIjOkOQG5EoQ==", "requires": { - "debug": "^4.2.0", + "debug": "^4.3.2", "sax": "^1.2.4" }, "dependencies": { "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } @@ -4647,6 +5601,12 @@ "unset-value": "^1.0.0" } }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true + }, "cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -4679,6 +5639,16 @@ } } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -4730,14 +5700,14 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, + "optional": true, "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" @@ -4747,14 +5717,15 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true + "dev": true, + "optional": true } } }, "caniuse-lite": { - "version": "1.0.30001093", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001093.tgz", - "integrity": "sha512-0+ODNoOjtWD5eS9aaIpf4K0gQqZfILNY4WSNuYzeT1sXni+lMrrVjc0odEobJt6wrODofDZUX8XYi/5y7+xl8g==", + "version": "1.0.30001198", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001198.tgz", + "integrity": "sha512-r5GGgESqOPZzwvdLVER374FpQu2WluCF1Z2DSiFJ89KSmGjT0LVKjgv4NcAqHmGWF9ihNpqRI9KXO9Ex4sKsgA==", "dev": true }, "case-sensitive-paths-webpack-plugin": { @@ -4800,11 +5771,33 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } } }, "chardet": { @@ -4849,9 +5842,9 @@ } }, "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", "dev": true, "optional": true, "requires": { @@ -4920,6 +5913,40 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, + "chrome-launcher": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^1.0.5", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^0.5.3", + "rimraf": "^3.0.2" + }, + "dependencies": { + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "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" + } + } + } + }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -4977,8 +6004,7 @@ "classnames": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==", - "dev": true + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" }, "clean-css": { "version": "4.2.3", @@ -4987,16 +6013,14 @@ "dev": true, "requires": { "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-boxes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", @@ -5056,6 +6080,15 @@ } } }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, "cli-width": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", @@ -5066,51 +6099,10 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "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 - }, - "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 - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "clone-deep": { @@ -5180,24 +6172,22 @@ } }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { - "color-name": "1.1.3" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "colorette": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.0.tgz", - "integrity": "sha512-soRSroY+OF/8OdA3PTQXwaDJeMc7TfknKKrxeSCencL2a4+Tx5zhxmmv7hdpCjhKBjehzp8+bwe/T68K0hpIjw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", "dev": true }, "colors": { @@ -5241,24 +6231,26 @@ "dev": true }, "compress-commons": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", - "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.0.tgz", + "integrity": "sha512-ofaaLqfraD1YRTkrRKPCrGJ1pFeDG/MVCkVVV2FNGeWquSlqw5wOrwOfPQ1xF2u+blpeWASie5EubHz+vsNIgA==", "dev": true, "requires": { - "buffer-crc32": "^0.2.1", - "crc32-stream": "^2.0.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } } } @@ -5313,8 +6305,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -5463,11 +6454,316 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "copy-webpack-plugin": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz", + "integrity": "sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "fast-glob": "^3.2.4", + "find-cache-dir": "^3.3.1", + "glob-parent": "^5.1.1", + "globby": "^11.0.1", + "loader-utils": "^2.0.0", + "normalize-path": "^3.0.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "@nodelib/fs.stat": { + "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 + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "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": { + "semver": "^6.0.0" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "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" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.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 + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "ssri": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "core-js": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" }, "core-js-compat": { "version": "3.6.5", @@ -5490,8 +6786,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "6.0.0", @@ -5511,6 +6806,7 @@ "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", "dev": true, + "optional": true, "requires": { "buffer": "^5.1.0" }, @@ -5520,6 +6816,7 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "dev": true, + "optional": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -5527,14 +6824,37 @@ } } }, - "crc32-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", - "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", "dev": true, "requires": { - "crc": "^3.4.4", - "readable-stream": "^2.0.0" + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, + "crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dev": true, + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "create-ecdh": { @@ -5593,6 +6913,15 @@ "object-assign": "^4.1.1" } }, + "crocket": { + "version": "0.9.11", + "resolved": "https://registry.npmjs.org/crocket/-/crocket-0.9.11.tgz", + "integrity": "sha1-KI/KEe8NPdI5tixIgmXzDI7fsMU=", + "dev": true, + "requires": { + "xpipe": "*" + } + }, "cross-env": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", @@ -5628,6 +6957,12 @@ "which": "^1.2.9" } }, + "cross-unzip": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/cross-unzip/-/cross-unzip-0.0.2.tgz", + "integrity": "sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8=", + "dev": true + }, "crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", @@ -5659,22 +6994,33 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "css-hot-loader": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/css-hot-loader/-/css-hot-loader-1.4.4.tgz", + "integrity": "sha512-J/qXHz+r7FOT92qMIJfxUk0LC9fecQNZVr0MswQ4FOpKLyOCBjofVMfc6R268bh/5ktkTShrweMr0wWqerC92g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" + "loader-utils": "^1.1.0", + "lodash": "^4.17.5", + "normalize-url": "^1.9.1" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true } } @@ -5715,24 +7061,9 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, - "css-parse": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", - "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", - "dev": true, - "requires": { - "css": "^2.0.0" - } - }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -5752,16 +7083,21 @@ "dev": true }, "css-selector-tokenizer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz", - "integrity": "sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw==", + "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", - "regexpu-core": "^4.6.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", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", + "dev": true + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -5770,14 +7106,6 @@ "requires": { "mdn-data": "2.0.4", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "css-value": { @@ -5822,12 +7150,6 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -5836,6 +7158,7 @@ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, + "optional": true, "requires": { "array-find-index": "^1.0.1" } @@ -5871,7 +7194,6 @@ "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" } @@ -5879,8 +7201,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -6083,9 +7404,9 @@ "dev": true }, "deepmerge": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.0.1.tgz", - "integrity": "sha512-VIPwiMJqJ13ZQfaCsIFnp5Me9tnjURiaIFxfz7EH0Ci0dTSQpZtSLrqOicXqEd/z2r+z+Klk9GzmnRsgpgbOsQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, "default-gateway": { @@ -6228,6 +7549,12 @@ "dev": true } } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true } } }, @@ -6310,6 +7637,37 @@ "integrity": "sha1-WiBc48Ky73e2I41roXnrdMag6Bg=", "dev": true }, + "devtools": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.12.1.tgz", + "integrity": "sha512-JyG46suEiZmld7/UVeogkCWM0zYGt+2ML/TI+SkEp+bTv9cs46cDb0pKF3glYZJA7wVVL2gC07Ic0iCxyJEnCQ==", + "dev": true, + "requires": { + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.12.0", + "@wdio/utils": "6.11.0", + "chrome-launcher": "^0.13.1", + "edge-paths": "^2.1.0", + "puppeteer-core": "^5.1.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } + } + }, + "devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true + }, "devtron": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/devtron/-/devtron-1.4.0.tgz", @@ -6346,6 +7704,35 @@ } } }, + "dir-compare": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz", + "integrity": "sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==", + "dev": true, + "requires": { + "buffer-equal": "1.0.0", + "colors": "1.0.3", + "commander": "2.9.0", + "minimatch": "3.0.4" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + } + } + }, "dir-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", @@ -6373,58 +7760,141 @@ } }, "dmg-builder": { - "version": "22.7.0", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.7.0.tgz", - "integrity": "sha512-5Ea2YEz6zSNbyGzZD+O9/MzmaXb6oa15cSKWo4JQ1xP4rorOpte7IOj2jcwYjtc+Los2gu1lvT314OC1OZIWgg==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.10.5.tgz", + "integrity": "sha512-58FEpfH8PEFqjbUNka4bYr52snRT8+LSXrP4gy6EZWOVICbOlmTOYj988pfoLam5C5iXb3odmyUQqwWOxlsEUw==", "dev": true, "requires": { - "app-builder-lib": "22.7.0", - "builder-util": "22.7.0", - "fs-extra": "^9.0.0", - "iconv-lite": "^0.5.1", - "js-yaml": "^3.14.0", + "app-builder-lib": "22.10.5", + "builder-util": "22.10.5", + "dmg-license": "^1.0.8", + "fs-extra": "^9.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.0.0", "sanitize-filename": "^1.6.3" }, "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, - "iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "argparse": "^2.0.1" } }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true } } }, + "dmg-license": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.8.tgz", + "integrity": "sha512-47GOb6b4yVzpovXC34heXElpH++ICg9GuWBeOTaokUNLAoAdWpE4VehudYEEtu96j2jXsgQWYf78nW7r+0Y3eg==", + "dev": true, + "optional": true, + "requires": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "cli-truncate": "^1.1.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.5", + "plist": "^3.0.1", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "optional": true + }, + "cli-truncate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-1.1.0.tgz", + "integrity": "sha512-bAtZo0u82gCfaAGfSNxUdTI9mNyza7D8w4CVCcaOsy7sgwDzvx6ekr6cuWJqY3UGzgnQ1+4wgENup5eIhgxEYA==", + "dev": true, + "optional": true, + "requires": { + "slice-ansi": "^1.0.0", + "string-width": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "optional": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "optional": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "optional": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -6472,7 +7942,6 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "dev": true, "requires": { "@babel/runtime": "^7.1.2" } @@ -6543,9 +8012,9 @@ } }, "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "requires": { "is-obj": "^2.0.0" @@ -6708,6 +8177,37 @@ "safer-buffer": "^2.1.0" } }, + "edge-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", + "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "dev": true, + "requires": { + "@types/which": "^1.3.2", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "editions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.1.0.tgz", + "integrity": "sha512-h6nWEyIocfgho9J3sTSuhU/WoFOu1hTX75rPBebNrbF38Y9QFDjCDizYXdikHTySW7Y3mSxli8bpDz9RAtc7rA==", + "dev": true, + "requires": { + "errlop": "^4.0.0", + "version-range": "^1.0.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6721,9 +8221,9 @@ "dev": true }, "electron": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-7.3.2.tgz", - "integrity": "sha512-5uSWVfCJogiPiU0G+RKi4ECnNs0gPNjAwYVE9KR7RXaOJYcpNIC5RFejaaUnuRoBssJ5B1n/5WU6wDUxvPajWQ==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-11.3.0.tgz", + "integrity": "sha512-MhdS0gok3wZBTscLBbYrOhLaQybCSAfkupazbK1dMP5c+84eVMxJE/QGohiWQkzs0tVFIJsAHyN19YKPbelNrQ==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -6732,45 +8232,35 @@ }, "dependencies": { "@types/node": { - "version": "12.12.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.47.tgz", - "integrity": "sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A==", + "version": "12.20.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.4.tgz", + "integrity": "sha512-xRCgeE0Q4pT5UZ189TJ3SpYuX/QGl6QIAOAIeDSbAVAd2gX1NxSZup4jNVK7cxIeP8KDSbJgcckun495isP1jQ==", "dev": true } } }, "electron-builder": { - "version": "22.7.0", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.7.0.tgz", - "integrity": "sha512-t6E3oMutpST64YWbZCg7HodEwJOsnjUF1vnDIHm2MW6CFZPX8tlCK6efqaV66LU0E0Nkp/JH6TE5bCqQ1+VdPQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.10.5.tgz", + "integrity": "sha512-0q/289UUJUhRou6lZKDz/wzK6WprIQ6VXMTmaI+w9qXvSNugPC9UA5s2zXInOkjZOvO/xKnjeyiavrVSHYF3tA==", "dev": true, "requires": { - "@types/yargs": "^15.0.5", - "app-builder-lib": "22.7.0", + "@types/yargs": "^15.0.13", + "app-builder-lib": "22.10.5", "bluebird-lst": "^1.0.9", - "builder-util": "22.7.0", - "builder-util-runtime": "8.7.1", - "chalk": "^4.0.0", - "dmg-builder": "22.7.0", - "fs-extra": "^9.0.0", + "builder-util": "22.10.5", + "builder-util-runtime": "8.7.3", + "chalk": "^4.1.0", + "dmg-builder": "22.10.5", + "fs-extra": "^9.1.0", "is-ci": "^2.0.0", "lazy-val": "^1.0.4", "read-config-file": "6.0.0", "sanitize-filename": "^1.6.3", - "update-notifier": "^4.1.0", - "yargs": "^15.3.1" + "update-notifier": "^5.1.0", + "yargs": "^16.2.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -6781,31 +8271,33 @@ "supports-color": "^7.1.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "has-flag": { @@ -6815,40 +8307,101 @@ "dev": true }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "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": { "has-flag": "^4.0.0" } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.6", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.6.tgz", + "integrity": "sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==", "dev": true } } }, "electron-chromedriver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/electron-chromedriver/-/electron-chromedriver-7.0.0.tgz", - "integrity": "sha512-7qymT0fn3VTit0peym1iz4Y+fTwq9EPsv1V9Qh+vQdoVqP/4SM9lOHrsBeuFN1JJADZLu7R119ZvMkP6EnLYhw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/electron-chromedriver/-/electron-chromedriver-11.0.0.tgz", + "integrity": "sha512-ayMJPBbB4puU0SqYbcD9XvF3/7GWIhqKE1n5lG2/GQPRnrZkNoPIilsrS0rQcD50Xhl69KowatDqLhUznZWtbA==", "dev": true, "requires": { - "electron-download": "^4.1.1", - "extract-zip": "^1.6.7" + "@electron/get": "^1.12.2", + "extract-zip": "^2.0.0" + }, + "dependencies": { + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "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==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "electron-connect": { @@ -6863,142 +8416,683 @@ "ws": "^3.1.0" } }, - "electron-download": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", - "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", - "dev": true, + "electron-context-menu": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.5.0.tgz", + "integrity": "sha512-kzvp8XUmbp2TG2hELJUl7Yjlq4Ag549JQu/C8mxvy1CmAU15UFmPC3bPdXMGE/e3xbi97shgxfttxeQ/6h4MoQ==", "requires": { - "debug": "^3.0.0", - "env-paths": "^1.0.0", - "fs-extra": "^4.0.1", - "minimist": "^1.2.0", - "nugget": "^2.0.1", - "path-exists": "^3.0.0", - "rc": "^1.2.1", - "semver": "^5.4.1", - "sumchecker": "^2.0.2" + "cli-truncate": "^2.1.0", + "electron-dl": "^3.1.0", + "electron-is-dev": "^1.2.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, + "electron-is-dev": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz", + "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" + } + } + }, + "electron-devtools-installer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-3.1.1.tgz", + "integrity": "sha512-g2D4J6APbpsiIcnLkFMyKZ6bOpEJ0Ltcc2m66F7oKUymyGAt628OWeU9nRZoh1cNmUs/a6Cls2UfOmsZtE496Q==", + "requires": { + "rimraf": "^3.0.2", + "semver": "^7.2.1", + "unzip-crx-3": "^0.2.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { - "ms": "^2.1.1" + "glob": "^7.1.3" } }, - "env-paths": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", - "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } + } + }, + "electron-dl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.1.0.tgz", + "integrity": "sha512-nY3vvxX5w11+cFT6JkJwMHtH6dk5sPtjRtzjaj4NrS9CyYF3atvVt8yMfZtXruULU1JAuxEEf2B8O6YgXs9xsQ==", + "requires": { + "ext-name": "^5.0.0", + "pupa": "^2.0.1", + "unused-filename": "^2.1.0" + } + }, + "electron-is-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-2.0.0.tgz", + "integrity": "sha512-3X99K852Yoqu9AcW50qz3ibYBWY79/pBhlMCab8ToEWS48R0T9tyxRiQhwylE7zQdXrMnx2JKqUJyMPmt5FBqA==" + }, + "electron-log": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.3.2.tgz", + "integrity": "sha512-PJPWE8JDzQ137UlxX9K917nI8GTcwgiJpE2PMPXZo+I6C4AaZU+JWQ3lW5NjQ1Lg8Qk8qbze+Ly0yAiqhbmpeA==" + }, + "electron-mocha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/electron-mocha/-/electron-mocha-10.0.0.tgz", + "integrity": "sha512-eCIAPlhSi10wPcwYnZWxRAKoEGKX+UQyco3gjWcWPD6csfwCLT5CTvA1bId8Q8Gb0ss8Rj8y8kuK9eLCJtr0DQ==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1", + "electron-window": "^0.8.0", + "fs-extra": "^9.0.1", + "log-symbols": "^4.0.0", + "mocha": "^8.2.1", + "which": "^2.0.2", + "yargs": "^16.1.1" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "fill-range": "^7.0.1" } }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" } }, - "sumchecker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", - "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "debug": "^2.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.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" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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 + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": 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-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "mocha": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.1.tgz", + "integrity": "sha512-5SBMxANWqOv5bw3Hx+HVgaWlcWcFEQDUdaUAr1AUU+qwtx6cowhn7gEDT/DwQP7uYxnvShdUOVLbTYAHOEGfDQ==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", + "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true } } }, "electron-notarize": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.1.1.tgz", - "integrity": "sha512-TpKfJcz4LXl5jiGvZTs5fbEx+wUFXV5u8voeG5WCHWfY/cdgdD8lDZIZRqLVOtR3VO+drgJ9aiSHIO9TYn/fKg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.0.0.tgz", + "integrity": "sha512-dsib1IAquMn0onCrNMJ6gtEIZn/azG8hZMCYOuZIMVMUeRMgBYHK1s5TK9P8xAcrAjh/2aN5WYHzgVSWX314og==", "dev": true, "requires": { "debug": "^4.1.1", - "fs-extra": "^8.0.1" + "fs-extra": "^9.0.1" }, "dependencies": { "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "requires": { + "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true } } }, "electron-publish": { - "version": "22.7.0", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.7.0.tgz", - "integrity": "sha512-hmU69xlb6vvAV3QfpHYDlkdZMFdBAgDbptoxbLFrnTq5bOkcL8AaDbvxeoZ4+lvqgs29NwqGpkHo2oN+p/hCfg==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.10.5.tgz", + "integrity": "sha512-dHyuazv3P3j1Xyv7pdwTwAvxWab2pCb0G0Oa6qWQoCc4b1/mRGY00M7AvYW1cPuUijj9zYAf1HmXfM6MifaMlA==", "dev": true, "requires": { - "@types/fs-extra": "^9.0.1", + "@types/fs-extra": "^9.0.7", "bluebird-lst": "^1.0.9", - "builder-util": "22.7.0", - "builder-util-runtime": "8.7.1", - "chalk": "^4.0.0", - "fs-extra": "^9.0.0", + "builder-util": "22.10.5", + "builder-util-runtime": "8.7.3", + "chalk": "^4.1.0", + "fs-extra": "^9.1.0", "lazy-val": "^1.0.4", - "mime": "^2.4.5" + "mime": "^2.5.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "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 + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + }, + "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": { + "has-flag": "^4.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, + "electron-to-chromium": { + "version": "1.3.514", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.514.tgz", + "integrity": "sha512-8vb8zKIeGlZigeDzNWWthmGeLzo5CC43Lc+CZshMs7UXFVMPNLtXJGa/txedpu3OJFrXXVheBwp9PqOJJlHQ8w==", + "dev": true + }, + "electron-updater": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.8.tgz", + "integrity": "sha512-/tB82Ogb2LqaXrUzAD8waJC+TZV52Pr0Znfj7w+i4D+jA2GgrKFI3Pxjp+36y9FcBMQz7kYsMHcB6c5zBJao+A==", + "requires": { + "@types/semver": "^7.3.4", + "builder-util-runtime": "8.7.3", + "fs-extra": "^9.1.0", + "js-yaml": "^4.0.0", + "lazy-val": "^1.0.4", + "lodash.isequal": "^4.5.0", + "semver": "^7.3.4" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "electron-webpack": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/electron-webpack/-/electron-webpack-2.8.2.tgz", + "integrity": "sha512-rR7hxoOlZIcJf6R08mVl/4TBtFY+HW6sv4Z28TdMWETvcM4ZBIBdGNGylxF0gNwB8gkTgo8UkkDbXGX48K4Vow==", + "dev": true, + "requires": { + "@types/webpack-env": "^1.15.1", + "async-exit-hook": "^2.0.1", + "bluebird": "^3.7.2", + "chalk": "^4.0.0", + "crocket": "^0.9.11", + "css-hot-loader": "^1.4.4", + "css-loader": "^3.4.2", + "debug": "^4.1.1", + "dotenv": "^8.2.0", + "dotenv-expand": "^5.1.0", + "electron-devtools-installer": "^2.2.4", + "electron-webpack-js": "~2.4.1", + "file-loader": "^6.0.0", + "fs-extra": "^9.0.0", + "html-loader": "^1.1.0", + "html-webpack-plugin": "^4.0.4", + "lazy-val": "^1.0.4", + "mini-css-extract-plugin": "^0.9.0", + "node-loader": "^0.6.0", + "read-config-file": "~4.0.1", + "semver": "^7.1.3", + "source-map-support": "^0.5.16", + "style-loader": "^1.1.3", + "terser-webpack-plugin": "^2.3.5", + "url-loader": "^4.0.0", + "webpack-cli": "^3.3.11", + "webpack-dev-server": "^3.10.3", + "webpack-merge": "^4.2.2", + "yargs": "^15.3.1" + }, + "dependencies": { + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "cacache": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", + "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "dev": true, + "requires": { + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "unique-filename": "^1.1.1" } }, "chalk": { @@ -7011,21 +9105,101 @@ "supports-color": "^7.1.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", "dev": true }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "electron-devtools-installer": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-2.2.4.tgz", + "integrity": "sha512-b5kcM3hmUqn64+RUcHjjr8ZMpHS2WJ5YO0pnG9+P/RTdx46of/JrEjuciHWux6pE+On6ynWhHJF53j/EDJN0PA==", + "dev": true, + "requires": { + "7zip": "0.0.6", + "cross-unzip": "0.0.2", + "rimraf": "^2.5.2", + "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 + } + } + }, + "file-loader": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", + "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, "fs-extra": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", @@ -7044,6 +9218,15 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, "jsonfile": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", @@ -7054,34 +9237,304 @@ "universalify": "^1.0.0" } }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "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": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "mini-css-extract-plugin": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", + "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "supports-color": { + "read-config-file": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-4.0.1.tgz", + "integrity": "sha512-5caED3uo2IAZMPcbh/9hx/O29s2430RLxtnFDdzxpH/epEpawOrQnGBHueotIXUrGPPIgdNQN+S/CIp2WmiSfw==", + "dev": true, + "requires": { + "ajv": "^6.10.1", + "ajv-keywords": "^3.4.1", + "dotenv": "^8.0.0", + "dotenv-expand": "^5.1.0", + "fs-extra": "^8.1.0", + "js-yaml": "^3.13.1", + "json5": "^2.1.0", + "lazy-val": "^1.0.4" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "ssri": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" + } + }, + "style-loader": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", + "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.6" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "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": { "has-flag": "^4.0.0" } }, + "terser-webpack-plugin": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.7.tgz", + "integrity": "sha512-xzYyaHUNhzgaAdBsXxk2Yvo/x1NJdslUaussK3fdpBbvttm1iIwU+c26dj9UxJcwk2c5UWt5F55MUTIA8BE7Dg==", + "dev": true, + "requires": { + "cacache": "^13.0.1", + "find-cache-dir": "^3.3.1", + "jest-worker": "^25.4.0", + "p-limit": "^2.3.0", + "schema-utils": "^2.6.6", + "serialize-javascript": "^3.1.0", + "source-map": "^0.6.1", + "terser": "^4.6.12", + "webpack-sources": "^1.4.3" + } + }, "universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true + }, + "url-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.0.tgz", + "integrity": "sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.26", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, - "electron-to-chromium": { - "version": "1.3.484", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.484.tgz", - "integrity": "sha512-esh5mmjAGl6HhAaYgHlDZme+jCIc+XIrLrBTwxviE+pM64UBmdLUIHLlrPzJGbit7hQI1TR/oGDQWCvQZ5yrFA==", - "dev": true + "electron-webpack-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/electron-webpack-js/-/electron-webpack-js-2.4.1.tgz", + "integrity": "sha512-NPbcI4nnuclkLEKmwRI8sui2GNe37NKm0pCQR6KZA7YSV3KQdH4I7wOgIZ2AkeCpyeUHrBSMGQY+VqhPD7OtMA==", + "dev": true, + "requires": { + "@babel/core": "^7.9.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/preset-env": "^7.9.0", + "babel-loader": "^8.1.0", + "babel-plugin-component": "^1.1.1" + } + }, + "electron-window": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/electron-window/-/electron-window-0.8.1.tgz", + "integrity": "sha1-FsoYfrSHCwZ5J0/IKZxZYOarLF4=", + "dev": true, + "requires": { + "is-electron-renderer": "^2.0.0" + } }, "elliptic": { "version": "6.5.3", @@ -7107,10 +9560,9 @@ } }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "emojis-list": { "version": "3.0.0", @@ -7125,12 +9577,12 @@ "dev": true }, "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "^0.6.2" } }, "end-of-stream": { @@ -7143,9 +9595,9 @@ } }, "enhanced-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz", - "integrity": "sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -7165,6 +9617,23 @@ } } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + } + } + }, "entities": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", @@ -7177,6 +9646,12 @@ "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", "dev": true }, + "errlop": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/errlop/-/errlop-4.1.0.tgz", + "integrity": "sha512-vul6gGBuVt0M2TPi1/WrcL86+Hb3Q2Tpu3TME3sbVhZrYf7J1ZMHCodI25RQKCVurh56qTfvgM0p3w5cT4reSQ==", + "dev": true + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -7274,16 +9749,15 @@ "dev": true }, "escalade": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.1.tgz", - "integrity": "sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", "dev": true }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" }, "escape-html": { "version": "1.0.3", @@ -7294,136 +9768,72 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz", + "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.2.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "file-entry-cache": "^6.0.0", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", + "levn": "^0.4.1", + "lodash": "^4.17.20", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.4", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "doctrine": { @@ -7435,29 +9845,37 @@ "esutils": "^2.0.2" } }, - "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 - }, "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" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "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==", + "dev": true + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "glob-parent": { @@ -7490,140 +9908,61 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "inquirer": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.0.tgz", - "integrity": "sha512-K+LZp6L/6eE5swqIcVXrxl21aGDU4S50gKH0/d96OMQnSBCyGyZl/oZhbkVmdp5sBoINHd4xZvFSARh2dk6DWA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "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==", + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "yallist": "^4.0.0" } }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } - } - }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "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": { "has-flag": "^4.0.0" @@ -7634,6 +9973,21 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -7664,6 +10018,64 @@ } } }, + "eslint-import-resolver-webpack": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.0.tgz", + "integrity": "sha512-hZWGcmjaJZK/WSCYGI/y4+FMGQZT+cwW/1E/P4rDwFj2PbanlQHISViw4ccDJ+2wxAqjgwBfxwy3seABbVKDEw==", + "dev": true, + "requires": { + "array-find": "^1.0.0", + "debug": "^2.6.9", + "enhanced-resolve": "^0.9.1", + "find-root": "^1.1.0", + "has": "^1.0.3", + "interpret": "^1.2.0", + "lodash": "^4.17.15", + "node-libs-browser": "^1.0.0 || ^2.0.0", + "resolve": "^1.13.1", + "semver": "^5.7.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "enhanced-resolve": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.2.0", + "tapable": "^0.1.8" + } + }, + "memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true + } + } + }, "eslint-module-utils": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", @@ -7743,43 +10155,34 @@ } } }, + "eslint-plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", + "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, "eslint-plugin-cypress": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.1.tgz", - "integrity": "sha512-MxMYoReSO5+IZMGgpBZHHSx64zYPSPTpXDwsgW7ChlJTF/sA+obqRbHplxD6sBStE+g4Mi0LCLkG4t9liu//mQ==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz", + "integrity": "sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA==", "dev": true, "requires": { "globals": "^11.12.0" } }, - "eslint-plugin-eslint-comments": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", - "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "ignore": "^5.0.5" - }, - "dependencies": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - } - } - }, "eslint-plugin-header": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.0.0.tgz", - "integrity": "sha512-OIu2ciVW8jK4Ove4JHm1I7X0C98PZuLCyCsoUhAm2HpyGS+zr34qLM6iV06unnDvssvvEh5BkOfaLRF+N7cGoQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.0.tgz", + "integrity": "sha512-jKKcwMsB0/ftBv3UVmuQir1f8AmXzTS9rdzPkileW8/Nz9ivdea8vOU1ZrMbX+WH6CpwnHEo3403baSHk40Mag==", "dev": true }, "eslint-plugin-import": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", - "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", "dev": true, "requires": { "array-includes": "^3.1.1", @@ -7787,7 +10190,7 @@ "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.3", + "eslint-import-resolver-node": "^0.3.4", "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", @@ -7824,30 +10227,60 @@ } } }, + "eslint-plugin-jquery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jquery/-/eslint-plugin-jquery-1.5.1.tgz", + "integrity": "sha512-L7v1eaK5t80C0lvUXPFP9MKnBOqPSKhCOYyzy4LZ0+iK+TJwN8S9gAkzzP1AOhypRIwA88HF6phQ9C7jnOpW8w==", + "dev": true + }, "eslint-plugin-mattermost": { - "version": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee", - "from": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee", + "version": "github:mattermost/eslint-plugin-mattermost#46ad99355644a719bf32082f472048f526605181", + "from": "github:mattermost/eslint-plugin-mattermost#46ad99355644a719bf32082f472048f526605181", + "dev": true + }, + "eslint-plugin-no-only-tests": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-2.4.0.tgz", + "integrity": "sha512-azP9PwQYfGtXJjW273nIxQH9Ygr+5/UyeW2wEjYoDtVYPI+WPKwbj0+qcAKYUXFZLRumq4HKkFaoDBAwBoXImQ==", "dev": true }, "eslint-plugin-react": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.3.tgz", - "integrity": "sha512-txbo090buDeyV0ugF3YMWrzLIUqpYTsWSDZV9xLSmExE1P/Kmgg9++PD931r+KEWS66O1c9R4srLVVHmeHpoAg==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz", + "integrity": "sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA==", "dev": true, "requires": { "array-includes": "^3.1.1", "array.prototype.flatmap": "^1.2.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.4.1", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", "object.entries": "^1.1.2", "object.fromentries": "^2.0.2", "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.17.0", + "resolve": "^1.18.1", "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } } }, + "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": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -7859,9 +10292,9 @@ } }, "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" @@ -7874,20 +10307,20 @@ "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, "dependencies": { "acorn": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", - "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true } } @@ -7899,18 +10332,18 @@ "dev": true }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } @@ -7949,9 +10382,9 @@ "dev": true }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "eventsource": { @@ -8036,6 +10469,12 @@ "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=", "dev": true }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "dev": true + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -8160,8 +10599,6 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", - "dev": true, - "optional": true, "requires": { "mime-db": "^1.28.0" } @@ -8170,8 +10607,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", - "dev": true, - "optional": true, "requires": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -8213,6 +10648,26 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } } }, "extglob": { @@ -8353,6 +10808,15 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fastq": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", + "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", @@ -8410,12 +10874,12 @@ } }, "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", + "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" } }, "file-loader": { @@ -8451,6 +10915,15 @@ "path-is-absolute": "^1.0.0", "rimraf": "^2.2.8" } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } } } }, @@ -8468,9 +10941,9 @@ "optional": true }, "filelist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", - "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", "dev": true, "requires": { "minimatch": "^3.0.4" @@ -8567,13 +11040,26 @@ "pkg-dir": "^3.0.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "requires": { - "locate-path": "^3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + } } }, "find-versions": { @@ -8598,21 +11084,26 @@ "resolve-dir": "^1.0.1" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "dependencies": { "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "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" @@ -8621,9 +11112,9 @@ } }, "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, "flush-write-stream": { @@ -8642,6 +11133,11 @@ "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==", "dev": true }, + "font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -8720,17 +11216,15 @@ "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" - }, - "dependencies": { - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - } + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" } }, "fs-write-stream-atomic": { @@ -8748,8 +11242,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -8807,15 +11300,43 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" - } - }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "requires": { - "globule": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "gensync": { @@ -8827,8 +11348,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-func-name": { "version": "2.0.0", @@ -8836,6 +11356,23 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, "get-proxy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", @@ -8850,7 +11387,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "3.0.0", @@ -8932,7 +11470,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9012,12 +11549,20 @@ } }, "global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", "dev": true, "requires": { - "ini": "^1.3.5" + "ini": "2.0.0" + }, + "dependencies": { + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + } } }, "global-modules": { @@ -9060,8 +11605,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globalthis": { "version": "1.0.1", @@ -9095,17 +11639,6 @@ } } }, - "globule": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", - "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", - "dev": true, - "requires": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" - } - }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -9139,15 +11672,13 @@ "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true, - "optional": true + "dev": true }, "grapheme-splitter": { "version": "1.0.4", @@ -9192,12 +11723,12 @@ "dev": true }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, @@ -9217,13 +11748,20 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbol-support-x": { "version": "1.4.2", @@ -9339,9 +11877,9 @@ "dev": true }, "highlight.js": { - "version": "9.18.5", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz", - "integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==", + "version": "9.18.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.3.tgz", + "integrity": "sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==", "dev": true }, "hmac-drbg": { @@ -9371,27 +11909,27 @@ } }, "hosted-git-info": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", - "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", "dev": true, "requires": { - "lru-cache": "^5.1.1" + "lru-cache": "^6.0.0" }, "dependencies": { "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" } }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } @@ -9421,6 +11959,81 @@ "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", "dev": true }, + "html-loader": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-1.1.0.tgz", + "integrity": "sha512-zwLbEgy+i7sgIYTlxI9M7jwkn29IvdsV6f1y7a2aLv/w8l1RigVk0PFijBZLLFsdi2gvL8sf2VJhTjLlfnK8sA==", + "dev": true, + "requires": { + "html-minifier-terser": "^5.0.5", + "htmlparser2": "^4.1.0", + "loader-utils": "^2.0.0", + "parse-srcset": "^1.0.2", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.1.0.tgz", + "integrity": "sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==", + "dev": true, + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, "html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", @@ -9575,25 +12188,114 @@ "sshpk": "^1.7.0" } }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "requires": { + "agent-base": "5", + "debug": "4" + } + }, "humanize-plus": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/humanize-plus/-/humanize-plus-1.8.2.tgz", "integrity": "sha1-pls0RZrWNnrbs3B6gqPJ+RYWcDA=", "dev": true }, + "iconv-corefoundation": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.5.tgz", + "integrity": "sha512-hI4m7udfV04OcjleOmDaR4gwXnH4xumxN+ZmywHDiKf2CmAzsT9SVYe7Y4pdnQbyZfXwAQyrElykbE5PrPRfmQ==", + "dev": true, + "optional": true, + "requires": { + "cli-truncate": "^1.1.0", + "node-addon-api": "^1.6.3" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "optional": true + }, + "cli-truncate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-1.1.0.tgz", + "integrity": "sha512-bAtZo0u82gCfaAGfSNxUdTI9mNyza7D8w4CVCcaOsy7sgwDzvx6ekr6cuWJqY3UGzgnQ1+4wgENup5eIhgxEYA==", + "dev": true, + "optional": true, + "requires": { + "slice-ansi": "^1.0.0", + "string-width": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "optional": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "optional": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "optional": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "icss-replace-symbols": { @@ -9621,12 +12323,6 @@ "source-map": "^0.6.1", "supports-color": "^5.4.0" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -9844,6 +12540,11 @@ "is-cwebp-readable": "^2.0.1" } }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "immer": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/immer/-/immer-1.7.2.tgz", @@ -9915,13 +12616,16 @@ "dev": true }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true }, "infer-owner": { "version": "1.0.4", @@ -9933,7 +12637,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -9942,13 +12645,12 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, "inquirer": { @@ -10066,7 +12768,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "requires": { "loose-envify": "^1.0.0" } @@ -10132,7 +12833,6 @@ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "optional": true, "requires": { "binary-extensions": "^2.0.0" } @@ -10158,6 +12858,15 @@ "ci-info": "^2.0.0" } }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-cwebp-readable": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-cwebp-readable/-/is-cwebp-readable-2.0.1.tgz", @@ -10228,6 +12937,12 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true + }, "is-dom": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz", @@ -10238,6 +12953,12 @@ "is-window": "^1.0.2" } }, + "is-electron-renderer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-electron-renderer/-/is-electron-renderer-2.0.1.tgz", + "integrity": "sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI=", + "dev": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -10254,16 +12975,13 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true + "dev": true, + "optional": true }, "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "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==" }, "is-gif": { "version": "3.0.0", @@ -10285,13 +13003,13 @@ } }, "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dev": true, "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" } }, "is-jpg": { @@ -10314,10 +13032,16 @@ "dev": true, "optional": true }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", "dev": true }, "is-number": { @@ -10387,9 +13111,7 @@ "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true, - "optional": true + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, "is-plain-object": { "version": "2.0.4", @@ -10476,7 +13198,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true + "dev": true, + "optional": true }, "is-window": { "version": "1.0.2", @@ -10505,14 +13228,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isbinaryfile": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", - "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -10554,6 +13270,17 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "istextorbinary": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-5.12.0.tgz", + "integrity": "sha512-wLDRWD7qpNTYubk04+q3en1+XZGS4vYWK0+SxNSXJLaITMMEK+J3o/TlOMyULeH1qozVZ9uUkKcyMA8odyxz8w==", + "dev": true, + "requires": { + "binaryextensions": "^4.15.0", + "editions": "^6.1.0", + "textextensions": "^5.11.0" + } + }, "isurl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", @@ -10601,6 +13328,33 @@ } } }, + "jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "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 + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -10610,8 +13364,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.0", @@ -10632,8 +13385,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-buffer": { "version": "3.0.0", @@ -10687,9 +13439,9 @@ } }, "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { "graceful-fs": "^4.1.6" @@ -10714,20 +13466,121 @@ } }, "jsx-ast-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", - "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", + "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", "dev": true, "requires": { - "array-includes": "^3.1.1", - "object.assign": "^4.1.0" + "array-includes": "^3.1.2", + "object.assign": "^4.1.2" + }, + "dependencies": { + "array-includes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", + "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "get-intrinsic": "^1.0.1", + "is-string": "^1.0.5" + } + }, + "es-abstract": { + "version": "1.18.0-next.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", + "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.1", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.3", + "string.prototype.trimstart": "^1.0.3" + } + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + } + } + }, + "jszip": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", + "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" } }, "keycode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", - "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=", - "dev": true + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" }, "keyv": { "version": "3.1.0", @@ -10790,8 +13643,7 @@ "lazy-val": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.4.tgz", - "integrity": "sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q==", - "dev": true + "integrity": "sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q==" }, "lazystream": { "version": "1.0.0", @@ -10818,13 +13670,48 @@ } }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, + "lighthouse-logger": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.2.0.tgz", + "integrity": "sha512-wzUvdIeJZhRsG6gpZfmSCfysaxNEr43i+QT+Hie94wvHDKFLi4n7C2GqZ4sTC+PH5b5iktmXJvU87rWvhP3lHw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "marky": "^1.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "lines-and-columns": { @@ -10859,6 +13746,12 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, @@ -10891,21 +13784,113 @@ } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", + "dev": true + }, + "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 + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", + "dev": true + }, + "lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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 + }, + "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": { + "has-flag": "^4.0.0" + } + } + } + }, "logalot": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz", @@ -10936,6 +13921,12 @@ "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", "dev": true }, + "loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -10947,7 +13938,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -10957,6 +13947,7 @@ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, + "optional": true, "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -10988,6 +13979,18 @@ "indent-string": "^2.1.0", "longest": "^1.0.0", "meow": "^3.3.0" + }, + "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, + "optional": true, + "requires": { + "repeating": "^2.0.0" + } + } } }, "lru-cache": { @@ -11026,7 +14029,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", @@ -11037,6 +14041,12 @@ "object-visit": "^1.0.0" } }, + "marky": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.1.tgz", + "integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==", + "dev": true + }, "matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -11117,6 +14127,7 @@ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, + "optional": true, "requires": { "camelcase-keys": "^2.0.0", "decamelize": "^1.1.2", @@ -11135,6 +14146,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, + "optional": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -11145,6 +14157,7 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -11158,6 +14171,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, + "optional": true, "requires": { "error-ex": "^1.2.0" } @@ -11167,6 +14181,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, + "optional": true, "requires": { "pinkie-promise": "^2.0.0" } @@ -11176,6 +14191,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -11186,13 +14202,15 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "dev": true, + "optional": 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, + "optional": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -11204,19 +14222,11 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, + "optional": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } } } }, @@ -11248,6 +14258,12 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -11308,8 +14324,7 @@ "mime-db": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" }, "mime-types": { "version": "2.1.27", @@ -11368,16 +14383,87 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + } } }, "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } }, "mississippi": { "version": "3.0.0", @@ -11440,11 +14526,16 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, "requires": { "minimist": "^1.2.5" } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", @@ -11586,6 +14677,11 @@ } } }, + "modify-filename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz", + "integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -11615,8 +14711,7 @@ "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 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multicast-dns": { "version": "6.2.3", @@ -11647,6 +14742,12 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -11679,9 +14780,9 @@ "dev": true }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, "nice-try": { @@ -11700,6 +14801,13 @@ "tslib": "^1.10.0" } }, + "node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "optional": true + }, "node-dir": { "version": "0.1.17", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", @@ -11760,6 +14868,12 @@ } } }, + "node-loader": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-0.6.0.tgz", + "integrity": "sha1-x5fvUQle1YWZArFX9jhPY2HgWug=", + "dev": true + }, "node-modules-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", @@ -11767,9 +14881,9 @@ "dev": true }, "node-releases": { - "version": "1.1.58", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", - "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==", + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", "dev": true }, "node-version": { @@ -11836,12 +14950,6 @@ } } }, - "npm-install-package": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz", - "integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=", - "dev": true - }, "npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -11859,6 +14967,30 @@ "string.prototype.padend": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -11919,6 +15051,12 @@ "normalize-package-data": "^2.3.2", "path-type": "^3.0.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, @@ -11952,38 +15090,6 @@ "boolbase": "~1.0.0" } }, - "nugget": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", - "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", - "dev": true, - "requires": { - "debug": "^2.1.3", - "minimist": "^1.1.0", - "pretty-bytes": "^1.0.2", - "progress-stream": "^1.1.0", - "request": "^2.45.0", - "single-line-log": "^1.1.2", - "throttleit": "0.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -12005,8 +15111,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -12161,7 +15266,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -12184,36 +15288,18 @@ "is-wsl": "^1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - } - } - }, "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "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" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, "optipng-bin": { @@ -12292,25 +15378,26 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "requires": { - "p-limit": "^2.0.0" + "p-limit": "^2.2.0" } }, "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } }, "p-map-series": { "version": "1.0.0", @@ -12357,8 +15444,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-json": { "version": "6.5.0", @@ -12383,8 +15469,7 @@ "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parallel-transform": { "version": "1.2.0", @@ -12431,9 +15516,9 @@ } }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", + "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -12448,6 +15533,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=", + "dev": true + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -12491,8 +15582,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -12559,8 +15649,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "optional": true + "dev": true }, "pidtree": { "version": "0.3.1", @@ -12605,6 +15694,36 @@ "dev": true, "requires": { "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + } } }, "pkg-up": { @@ -12661,6 +15780,27 @@ } } }, + "plist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", + "integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==", + "dev": true, + "optional": true, + "requires": { + "base64-js": "^1.2.3", + "xmlbuilder": "^9.0.7", + "xmldom": "0.1.x" + }, + "dependencies": { + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true, + "optional": true + } + } + }, "pngquant-bin": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/pngquant-bin/-/pngquant-bin-5.0.2.tgz", @@ -12707,14 +15847,14 @@ } }, "portfinder": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", - "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dev": true, "requires": { "async": "^2.6.2", "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.5" }, "dependencies": { "debug": { @@ -12745,12 +15885,6 @@ "supports-color": "^6.1.0" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -12852,12 +15986,6 @@ "source-map": "^0.6.1", "supports-color": "^5.4.0" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -12881,12 +16009,6 @@ "source-map": "^0.6.1", "supports-color": "^5.4.0" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -12910,12 +16032,6 @@ "source-map": "^0.6.1", "supports-color": "^5.4.0" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -12939,15 +16055,20 @@ "source-map": "^0.6.1", "supports-color": "^5.4.0" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, "postcss-value-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", @@ -12955,9 +16076,9 @@ "dev": true }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prepend-http": { @@ -12966,16 +16087,6 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, - "pretty-bytes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", - "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.1.0" - } - }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", @@ -12992,6 +16103,12 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "dev": true + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -13007,8 +16124,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -13016,67 +16132,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "progress-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", - "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", - "dev": true, - "requires": { - "speedometer": "~0.1.2", - "through2": "~0.2.3" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", - "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9", - "xtend": "~2.1.1" - } - }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "requires": { - "object-keys": "~0.4.0" - } - } - } - }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -13126,13 +16181,31 @@ "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.8.1" } }, + "prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "requires": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "dependencies": { + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -13150,6 +16223,12 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13230,14 +16309,86 @@ "dev": true }, "pupa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", - "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", - "dev": true, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", "requires": { "escape-goat": "^2.0.0" } }, + "puppeteer-core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", + "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "dependencies": { + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "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==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "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" + } + }, + "ws": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", + "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==", + "dev": true + } + } + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -13251,13 +16402,11 @@ "dev": true }, "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", "dev": true, - "optional": true, "requires": { - "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", "strict-uri-encode": "^1.0.0" } @@ -13280,6 +16429,12 @@ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, "ramda": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.21.0.tgz", @@ -13321,6 +16476,17 @@ "http-errors": "1.7.2", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "raw-loader": { @@ -13345,13 +16511,31 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2" } }, + "react-bootstrap": { + "version": "0.32.4", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.4.tgz", + "integrity": "sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==", + "requires": { + "@babel/runtime-corejs2": "^7.0.0", + "classnames": "^2.2.5", + "dom-helpers": "^3.2.0", + "invariant": "^2.2.4", + "keycode": "^2.2.0", + "prop-types": "^15.6.1", + "prop-types-extra": "^1.0.1", + "react-overlays": "^0.8.0", + "react-prop-types": "^0.4.0", + "react-transition-group": "^2.0.0", + "uncontrollable": "^5.0.0", + "warning": "^3.0.0" + } + }, "react-dev-utils": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-6.1.1.tgz", @@ -13405,6 +16589,15 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", @@ -13433,6 +16626,21 @@ "supports-color": "^5.3.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -13471,6 +16679,15 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", "dev": true }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, "inquirer": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", @@ -13515,6 +16732,16 @@ "json5": "^0.5.0" } }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -13530,6 +16757,15 @@ "is-wsl": "^1.1.0" } }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -13577,12 +16813,6 @@ "private": "~0.1.5", "source-map": "~0.6.1" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -13590,7 +16820,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13630,14 +16859,12 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "dev": true + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-modal": { "version": "3.11.2", @@ -13649,12 +16876,43 @@ "prop-types": "^15.5.10", "react-lifecycles-compat": "^3.0.0", "warning": "^4.0.3" + }, + "dependencies": { + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, + "react-overlays": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", + "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==", + "requires": { + "classnames": "^2.2.5", + "dom-helpers": "^3.2.1", + "prop-types": "^15.5.10", + "prop-types-extra": "^1.0.1", + "react-transition-group": "^2.2.0", + "warning": "^3.0.0" + } + }, + "react-prop-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", + "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", + "requires": { + "warning": "^3.0.0" } }, "react-smooth-dnd": { "version": "github:mattermost/react-smooth-dnd#af6b471295007274560a375799622c1cd52d678a", "from": "github:mattermost/react-smooth-dnd#af6b471295007274560a375799622c1cd52d678a", - "dev": true, "requires": { "prop-types": ">=15.6.0", "smooth-dnd": "0.12.1" @@ -13694,7 +16952,6 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "dev": true, "requires": { "dom-helpers": "^3.4.0", "loose-envify": "^1.4.0", @@ -13831,7 +17088,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13842,6 +17098,15 @@ "util-deprecate": "~1.0.1" } }, + "readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "readdirp": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", @@ -13869,12 +17134,6 @@ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.3.tgz", "integrity": "sha512-XA5o5dsNw8MhyW0Q7MWXJWc4oOzZKbdsEJq45h7c8q/d9DwWZ5F2ugUc1PuMLPGsUnphCt/cNDHu8JeBbxf1qA==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -13901,9 +17160,22 @@ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, + "optional": true, "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" + }, + "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, + "optional": true, + "requires": { + "repeating": "^2.0.0" + } + } } }, "redux": { @@ -13932,10 +17204,9 @@ } }, "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regenerator-transform": { "version": "0.14.5", @@ -13967,9 +17238,9 @@ } }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, "regexpu-core": { @@ -13987,9 +17258,9 @@ } }, "registry-auth-token": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", - "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", "dev": true, "requires": { "rc": "^1.2.8" @@ -14056,6 +17327,23 @@ "htmlparser2": "^3.3.0", "strip-ansi": "^3.0.0", "utila": "^0.4.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "repeat-element": { @@ -14075,6 +17363,7 @@ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, + "optional": true, "requires": { "is-finite": "^1.0.0" } @@ -14124,14 +17413,18 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "requires-port": { "version": "1.0.0", @@ -14148,6 +17441,12 @@ "path-parse": "^1.0.6" } }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==", + "dev": true + }, "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", @@ -14196,6 +17495,23 @@ "lowercase-keys": "^1.0.0" } }, + "resq": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.10.0.tgz", + "integrity": "sha512-hCUd0xMalqtPDz4jXIqs0M5Wnv/LZXN8h7unFOo4/nvExT9dDPbhwd3udRxLlp0HgBnHcV009UlduE9NZi7A6w==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + } + } + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -14218,10 +17534,16 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rgb2hex": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", - "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.3.tgz", + "integrity": "sha512-clEe0m1xv+Tva1B/TOepuIcvLAxP0U+sCDfgt1SX1HmI2Ahr5/Cd/nzJM1e78NKVtWdoo0s33YehpFA8UfIShQ==", "dev": true }, "rimraf": { @@ -14244,13 +17566,13 @@ } }, "roarr": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz", - "integrity": "sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==", + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", "dev": true, "optional": true, "requires": { - "boolean": "^3.0.0", + "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", @@ -14273,6 +17595,12 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -14282,21 +17610,6 @@ "aproba": "^1.1.1" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "*" - } - }, "rxjs": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", @@ -14309,8 +17622,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -14339,14 +17651,12 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "scheduler": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -14403,8 +17713,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-compare": { "version": "1.0.0", @@ -14610,8 +17919,12 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" }, "set-value": { "version": "2.0.1", @@ -14747,15 +18060,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "single-line-log": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", - "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", - "dev": true, - "requires": { - "string-width": "^1.0.1" - } - }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -14763,29 +18067,26 @@ "dev": true }, "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" } }, + "smart-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", + "dev": true, + "optional": true + }, "smooth-dnd": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/smooth-dnd/-/smooth-dnd-0.12.1.tgz", - "integrity": "sha512-Dndj/MOG7VP83mvzfGCLGzV2HuK1lWachMtWl/Iuk6zV7noDycIBnflwaPuDzoaapEl3Pc4+ybJArkkx9sxPZg==", - "dev": true + "integrity": "sha512-Dndj/MOG7VP83mvzfGCLGzV2HuK1lWachMtWl/Iuk6zV7noDycIBnflwaPuDzoaapEl3Pc4+ybJArkkx9sxPZg==" }, "snapdragon": { "version": "0.8.2", @@ -14835,6 +18136,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -14975,8 +18282,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "optional": true, "requires": { "is-plain-obj": "^1.0.0" } @@ -14985,8 +18290,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", - "dev": true, - "optional": true, "requires": { "sort-keys": "^1.0.0" } @@ -14998,9 +18301,9 @@ "dev": true }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-resolve": { @@ -15024,14 +18327,6 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "source-map-url": { @@ -15122,24 +18417,18 @@ } }, "spectron": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/spectron/-/spectron-9.0.0.tgz", - "integrity": "sha512-aMxprQ+5/8hDl27P6FafIuuL8jAueJ7WEc6S6pEEQNU7xGCMcfj0RY6TB1i9BtkazMymIxAkmwqlK233Fbhcgw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/spectron/-/spectron-13.0.0.tgz", + "integrity": "sha512-7RPa6Fp8gqL4V0DubobnqIRFHIijkpjg6MFHcJlxoerWyvLJd+cQvOh756XpB1Z/U3DyA9jPcS+HE2PvYRP5+A==", "dev": true, "requires": { "dev-null": "^0.1.1", - "electron-chromedriver": "^7.0.0", - "request": "^2.87.0", - "split": "^1.0.0", - "webdriverio": "^4.13.0" + "electron-chromedriver": "^11.0.0", + "request": "^2.88.2", + "split": "^1.0.1", + "webdriverio": "^6.9.1" } }, - "speedometer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", - "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", - "dev": true - }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -15176,6 +18465,13 @@ "lpad-align": "^1.0.1" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "optional": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -15197,6 +18493,16 @@ "supports-color": "^2.0.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -15314,18 +18620,16 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true, - "optional": true + "dev": true }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "string.prototype.matchall": { @@ -15386,25 +18690,27 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" } }, "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "optional": true, + "requires": { + "is-utf8": "^0.2.0" + } }, "strip-dirs": { "version": "2.1.0", @@ -15427,6 +18733,7 @@ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, + "optional": true, "requires": { "get-stdin": "^4.0.1" } @@ -15470,7 +18777,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -15597,47 +18903,50 @@ } }, "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "ajv": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", + "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "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 + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "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-regex": "^4.1.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" } } } @@ -15648,11 +18957,105 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } + } + }, "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15690,15 +19093,6 @@ "jsonfile": "^4.0.0", "universalify": "^0.1.0" } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } } } }, @@ -15731,14 +19125,6 @@ "commander": "^2.20.0", "source-map": "~0.6.1", "source-map-support": "~0.5.12" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "terser-webpack-plugin": { @@ -15756,14 +19142,6 @@ "terser": "^4.1.2", "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "text-table": { @@ -15772,10 +19150,10 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "throttleit": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", - "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "textextensions": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-5.12.0.tgz", + "integrity": "sha512-IYogUDaP65IXboCiPPC0jTLLBzYlhhw2Y4b0a2trPgbHNGGGEfuHE6tds+yDcCf4mpNDaGISFzwSSezcXt+d6w==", "dev": true }, "through": { @@ -15816,15 +19194,6 @@ "setimmediate": "^1.0.4" } }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -15835,13 +19204,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", @@ -15917,7 +19286,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true + "dev": true, + "optional": true }, "trim-repeated": { "version": "1.0.0", @@ -15958,6 +19328,12 @@ "requires": { "minimist": "^1.2.0" } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true } } }, @@ -15967,6 +19343,15 @@ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", "dev": true }, + "tsutils": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -15996,12 +19381,12 @@ "dev": true }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-detect": { @@ -16042,6 +19427,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "dev": true + }, "ua-parser-js": { "version": "0.7.21", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", @@ -16059,7 +19450,6 @@ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, - "optional": true, "requires": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -16070,7 +19460,6 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "dev": true, - "optional": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -16078,6 +19467,19 @@ } } }, + "uncontrollable": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz", + "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==", + "requires": { + "invariant": "^2.2.4" + } + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==" + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -16118,6 +19520,12 @@ "set-value": "^2.0.1" } }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -16203,6 +19611,37 @@ } } }, + "untildify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", + "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==" + }, + "unused-filename": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-2.1.0.tgz", + "integrity": "sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==", + "requires": { + "modify-filename": "^1.1.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + } + } + }, + "unzip-crx-3": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/unzip-crx-3/-/unzip-crx-3-0.2.0.tgz", + "integrity": "sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==", + "requires": { + "jszip": "^3.1.0", + "mkdirp": "^0.5.1", + "yaku": "^0.16.6" + } + }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -16210,62 +19649,53 @@ "dev": true }, "update-notifier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", - "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", "dev": true, "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", + "boxen": "^5.0.0", + "chalk": "^4.1.0", "configstore": "^5.0.1", "has-yarn": "^2.1.0", "import-lazy": "^2.1.0", "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", "semver-diff": "^3.1.1", "xdg-basedir": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.0.0.tgz", + "integrity": "sha512-5bvsqw+hhgUi3oYGK0Vf4WpIkyemp60WBInn7+WNfoISzAqk/HX4L7WNROq38E6UR/y3YADpv6pEm4BfkeEAdA==", "dev": true, "requires": { "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.0", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" } }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -16273,30 +19703,9 @@ } }, "cli-boxes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", - "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true }, "has-flag": { @@ -16305,51 +19714,37 @@ "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 - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "yallist": "^4.0.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "lru-cache": "^6.0.0" } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "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": { "has-flag": "^4.0.0" } }, - "term-size": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true - }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "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 }, "widest-line": { @@ -16360,6 +19755,23 @@ "requires": { "string-width": "^4.0.0" } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -16473,8 +19885,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -16510,6 +19921,11 @@ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, + "valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -16555,6 +19971,21 @@ "extsprintf": "^1.2.0" } }, + "version-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/version-compare/-/version-compare-1.1.0.tgz", + "integrity": "sha512-zVKtPOJTC9x23lzS4+4D7J+drq80BXVYAmObnr5zqxxFVH7OffJ1lJlAS7LYsQNV56jx/wtbw0UV7XHLrvd6kQ==", + "dev": true + }, + "version-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-1.1.0.tgz", + "integrity": "sha512-R1Ggfg2EXamrnrV3TkZ6yBNgITDbclB3viwSjbZ3+eK0VVNK4ajkYJTnDz5N0bIMYDtK9MUBvXJUnKO5RWWJ6w==", + "dev": true, + "requires": { + "version-compare": "^1.0.0" + } + }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -16562,21 +19993,20 @@ "dev": true }, "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", "requires": { "loose-envify": "^1.0.0" } }, "watchpack": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", - "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", "dev": true, "requires": { - "chokidar": "^3.4.0", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", "neo-async": "^2.5.0", "watchpack-chokidar2": "^2.0.0" @@ -16687,139 +20117,217 @@ "minimalistic-assert": "^1.0.0" } }, - "wdio-dot-reporter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/wdio-dot-reporter/-/wdio-dot-reporter-0.0.10.tgz", - "integrity": "sha512-A0TCk2JdZEn3M1DSG9YYbNRcGdx/YRw19lTiRpgwzH4qqWkO/oRDZRmi3Snn4L2j54KKTfPalBhlOtc8fojVgg==", - "dev": true - }, - "webdriverio": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.14.4.tgz", - "integrity": "sha512-Knp2vzuzP5c5ybgLu+zTwy/l1Gh0bRP4zAr8NWcrStbuomm9Krn9oRF0rZucT6AyORpXinETzmeowFwIoo7mNA==", + "webdriver": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.12.1.tgz", + "integrity": "sha512-3rZgAj9o2XHp16FDTzvUYaHelPMSPbO1TpLIMUT06DfdZjNYIzZiItpIb/NbQDTPmNhzd9cuGmdI56WFBGY2BA==", "dev": true, "requires": { - "archiver": "~2.1.0", - "babel-runtime": "^6.26.0", - "css-parse": "^2.0.0", - "css-value": "~0.0.1", - "deepmerge": "~2.0.1", - "ejs": "~2.5.6", - "gaze": "~1.1.2", - "glob": "~7.1.1", - "grapheme-splitter": "^1.0.2", - "inquirer": "~3.3.0", - "json-stringify-safe": "~5.0.1", - "mkdirp": "~0.5.1", - "npm-install-package": "~2.1.0", - "optimist": "~0.6.1", - "q": "~1.5.0", - "request": "^2.83.0", - "rgb2hex": "^0.1.9", - "safe-buffer": "~5.1.1", - "supports-color": "~5.0.0", - "url": "~0.11.0", - "wdio-dot-reporter": "~0.0.8", - "wgxpath": "~1.0.0" + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.12.0", + "@wdio/utils": "6.11.0", + "got": "^11.0.2", + "lodash.merge": "^4.6.1" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "ejs": { - "version": "2.5.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", - "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==", - "dev": true - }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { + "@sindresorhus/is": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "defer-to-connect": "^2.0.0" } }, - "supports-color": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.0.1.tgz", - "integrity": "sha512-7FQGOlSQ+AQxBNXJpVDj8efTA/FtyB5wcNE1omXXJ0cq6jm1jjDwuROlYDbnzHqdNPqliWFhcioCWSyav+xBnA==", + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "dev": true, + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + }, + "p-cancelable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==", + "dev": true + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" } } } }, + "webdriverio": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.12.1.tgz", + "integrity": "sha512-Nx7ge0vTWHVIRUbZCT+IuMwB5Q0Q5nLlYdgnmmJviUKLuc3XtaEBkYPTbhHWHgSBXsPZMIc023vZKNkn+6iyeQ==", + "dev": true, + "requires": { + "@types/puppeteer-core": "^5.4.0", + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/repl": "6.11.0", + "@wdio/utils": "6.11.0", + "archiver": "^5.0.0", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "6.12.1", + "fs-extra": "^9.0.1", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^5.1.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.3", + "serialize-error": "^8.0.0", + "webdriver": "6.12.1" + }, + "dependencies": { + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "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 + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, "webpack": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", - "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", @@ -16830,7 +20338,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.1.0", + "enhanced-resolve": "^4.3.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -16843,7 +20351,7 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.1", + "watchpack": "^1.7.4", "webpack-sources": "^1.4.1" } }, @@ -16872,6 +20380,15 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -16883,6 +20400,21 @@ "wrap-ansi": "^5.1.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -16896,6 +20428,21 @@ "which": "^1.2.9" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -16922,6 +20469,25 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -17055,11 +20621,20 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -17118,6 +20693,12 @@ "wrap-ansi": "^5.1.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -17129,6 +20710,27 @@ } } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "eventsource": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", @@ -17138,6 +20740,15 @@ "original": "^1.0.0" } }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, "fsevents": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", @@ -17164,6 +20775,25 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -17217,6 +20847,12 @@ "strip-ansi": "^5.1.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -17228,6 +20864,15 @@ } } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -17248,6 +20893,12 @@ "strip-ansi": "^5.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -17308,6 +20959,23 @@ "html-entities": "^1.2.0", "querystring": "^0.2.0", "strip-ansi": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "webpack-log": { @@ -17337,14 +21005,6 @@ "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "websocket-driver": { @@ -17364,16 +21024,10 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "wgxpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wgxpath/-/wgxpath-1.0.0.tgz", - "integrity": "sha1-7vikudVYzEla06mit1FZfs2a9pA=", - "dev": true - }, "whatwg-fetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.1.0.tgz", - "integrity": "sha512-pgmbsVWKpH9GxLXZmtdowDIqtb/rvPyjjQv3z9wLcmgWKFHilKnZD3ldgrOlwJoPGOUluQsRPWd52yVkPfmI1A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz", + "integrity": "sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w==", "dev": true }, "which": { @@ -17388,8 +21042,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { "version": "1.1.3", @@ -17398,6 +21051,39 @@ "dev": true, "requires": { "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "widest-line": { @@ -17442,18 +21128,22 @@ } } }, + "winreg": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", + "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" + }, + "winreg-utf8": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/winreg-utf8/-/winreg-utf8-0.1.1.tgz", + "integrity": "sha512-A4tBCO0bx3FCA/s8RowK40h3BQhKOWRFmsn6gYrXj6WjRAbOGnNJkTOpVmtA82UeGNMtpn3Wr0TWb742C1AS+g==" + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -17463,96 +21153,26 @@ "errno": "~0.1.7" } }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "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 - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", @@ -17589,6 +21209,26 @@ "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", "dev": true }, + "xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "optional": true + }, + "xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", + "dev": true, + "optional": true + }, + "xpipe": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/xpipe/-/xpipe-1.0.5.tgz", + "integrity": "sha1-jdi/Rfw/f1Xw4FS4ePQ6YmFNr98=", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -17598,8 +21238,12 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yaku": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/yaku/-/yaku-0.16.7.tgz", + "integrity": "sha1-HRlceKqbW/hHnIlblQT9TwhHmE4=" }, "yallist": { "version": "2.1.2", @@ -17614,10 +21258,9 @@ "dev": true }, "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -17629,93 +21272,50 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } + "yargs-parser": "^18.1.2" } }, "yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + } + } + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -17726,16 +21326,34 @@ "fd-slicer": "~1.1.0" } }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, "zip-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", - "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "dev": true, "requires": { - "archiver-utils": "^1.3.0", - "compress-commons": "^1.2.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0" + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } } } diff --git a/package.json b/package.json index 5e709854..2ca66d54 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,11 @@ "productName": "Mattermost", "version": "4.7.0-develop", "description": "Mattermost", - "main": "main.js", + "main": "dist/index.js", "author": "Mattermost, Inc. ", "license": "Apache-2.0", + "desktopName": "Mattermost.Desktop", + "homepage": "https://about.mattermost.com", "engines": { "node": ">=4.2.0" }, @@ -13,26 +15,39 @@ "type": "git", "url": "git://github.com/mattermost/desktop.git" }, + "config": { + "target": "11.3.0", + "arch": "x64", + "target_arch": "x64", + "disturl": "https://electronjs.org/headers", + "runtime": "electron", + "build_from_source": true + }, "scripts": { - "postinstall": "electron-builder install-app-deps && npm run extract-dict", - "extract-dict": "node scripts/extract_dict.js src/node_modules/simple-spellchecker/dict", "build": "npm-run-all build:*", "build:main": "webpack-cli --bail --config webpack.config.main.js", "build:renderer": "webpack-cli --bail --config webpack.config.renderer.js", - "start": "electron src --disable-dev-mode", + "build-prod": "npm-run-all build:*", + "start": "electron dist/ --disable-dev-mode", "restart": "npm run build && npm run start", "storybook": "start-storybook -p 9001 -c src/.storybook", - "clean": "rm -rf release/ node_modules/ src/node_modules/ && find src -name '*_bundle.js' | xargs rm", + "clean": "rm -rf release/ node_modules/ dist/ && find src -name '*_bundle.js' | xargs rm", "clean-install": "npm run clean && npm install", + "clean-dist": "rm -rf dist/", "watch": "run-p watch:*", "watch:main": "node scripts/watch_main_and_preload.js", "watch:renderer": "webpack-dev-server --config webpack.config.renderer.js", - "test": "npm-run-all lint:js test:*", - "test:app": "cross-env NODE_ENV=production npm run build && mocha -r @babel/register --reporter mocha-circleci-reporter --recursive test/specs", + "test": "npm-run-all lint:js test:unit test:e2e", + "test:e2e": "npm-run-all test:e2e:build test:e2e:run", + "test:e2e:build": "cross-env NODE_ENV=test npm run build", + "test:e2e:run": "cross-env NODE_ENV=test electron-mocha -r @babel/register --reporter mocha-circleci-reporter --recursive test/specs", + "test:unit": "npm-run-all test:unit:build test:unit:run", + "test:unit:build": "cross-env NODE_ENV=test webpack-cli --bail --config webpack.config.test.js", + "test:unit:run": "cross-env NODE_ENV=test mocha --reporter mocha-circleci-reporter dist/tests/test_bundle.js", "package:all": "cross-env NODE_ENV=production npm-run-all check-build-config package:windows package:mac package:linux", - "package:windows": "cross-env NODE_ENV=production npm-run-all check-build-config build && electron-builder --win --x64 --ia32 --publish=never", - "package:mac": "cross-env NODE_ENV=production npm-run-all check-build-config build && electron-builder --mac --publish=never", - "package:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build && electron-builder --linux --x64 --ia32 --publish=never", + "package:windows": "cross-env NODE_ENV=production npm-run-all check-build-config build-prod && electron-builder --win --x64 --ia32 --publish=never", + "package:mac": "cross-env NODE_ENV=production npm-run-all check-build-config build-prod && electron-builder --mac --publish=never", + "package:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build-prod && electron-builder --linux --x64 --ia32 --publish=never", "lint:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --ext .js --ext .jsx .", "lint:js-quiet": "eslint --ignore-path .gitignore --ignore-pattern node_modules --ext .js --ext .jsx . --quiet", "fix:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet --ext .js --ext .jsx . --fix", @@ -44,42 +59,73 @@ "@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-proposal-object-rest-spread": "^7.2.0", "@babel/preset-env": "^7.2.0", - "@babel/preset-react": "^7.0.0", + "@babel/preset-react": "^7.10.4", "@babel/register": "^7.0.0", "@storybook/addon-actions": "^4.0.11", "@storybook/react": "^4.0.11", + "@typescript-eslint/eslint-plugin": "4.15.0", + "@typescript-eslint/parser": "4.15.0", + "awesome-node-loader": "^1.1.1", "babel-eslint": "^10.0.3", "babel-loader": "^8.0.4", "chai": "^4.2.0", + "copy-webpack-plugin": "^6.2.1", "cross-env": "^5.2.0", "css-loader": "^1.0.1", "devtron": "^1.4.0", - "electron": "^7.3.2", - "electron-builder": "^22.2.0", + "electron": "^11.3.0", + "electron-builder": "^22.10.5", "electron-connect": "^0.6.3", - "electron-notarize": "^0.1.1", - "eslint": "^6.6.0", - "eslint-plugin-cypress": "^2.7.0", - "eslint-plugin-eslint-comments": "^3.1.2", - "eslint-plugin-header": "^3.0.0", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#070ce792d105482ffb2b27cfc0b7e78b3d20acee", - "eslint-plugin-react": "^7.16.0", + "electron-mocha": "^10.0.0", + "electron-notarize": "^1.0.0", + "electron-webpack": "^2.8.2", + "eslint": "7.19.0", + "eslint-import-resolver-webpack": "0.13.0", + "eslint-plugin-babel": "5.3.1", + "eslint-plugin-cypress": "2.11.2", + "eslint-plugin-header": "3.1.0", + "eslint-plugin-import": "2.22.1", + "eslint-plugin-jquery": "1.5.1", + "eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#46ad99355644a719bf32082f472048f526605181", + "eslint-plugin-no-only-tests": "2.4.0", + "eslint-plugin-react": "7.22.0", "file-loader": "^2.0.0", "image-webpack-loader": "5.0.0", "mdi-react": "^6.2.0", "mocha": "^5.2.0", "mocha-circleci-reporter": "0.0.3", "npm-run-all": "^4.1.5", + "spectron": "^13.0.0", + "style-loader": "^0.23.1", + "typescript": "4.1.3", + "url-loader": "^1.1.2", + "webpack": "^4.44.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.11.0", + "webpack-merge": "^4.1.4" + }, + "dependencies": { + "@hapi/joi": "^16.1.8", + "auto-launch": "^5.0.5", + "bootstrap": "^3.3.7", + "brace-expansion": "^2.0.0", + "classnames": "^2.2.6", + "electron-context-menu": "^2.5.0", + "electron-devtools-installer": "^3.1.1", + "electron-is-dev": "^2.0.0", + "electron-log": "^4.3.2", + "electron-updater": "4.3.8", + "font-awesome": "^4.7.0", + "prop-types": "^15.6.2", "react": "^16.6.3", + "react-bootstrap": "~0.32.4", "react-dom": "^16.6.3", "react-smooth-dnd": "github:mattermost/react-smooth-dnd#af6b471295007274560a375799622c1cd52d678a", - "spectron": "^9.0.0", - "style-loader": "^0.23.1", - "url-loader": "^1.1.2", - "webpack": "^4.43.0", - "webpack-cli": "^3.1.2", - "webpack-dev-server": "^3.1.14", - "webpack-merge": "^4.1.4" + "react-transition-group": "^2.5.0", + "semver": "^5.5.0", + "underscore": "^1.9.1", + "valid-url": "^1.0.9", + "winreg-utf8": "^0.1.1", + "yargs": "^15.3.1" } } diff --git a/scripts/afterpack.js b/scripts/afterpack.js index a6923392..0f89dfc6 100644 --- a/scripts/afterpack.js +++ b/scripts/afterpack.js @@ -8,16 +8,16 @@ const {spawn} = require('electron-notarize/lib/spawn.js'); const SETUID_PERMISSIONS = '4755'; exports.default = async function afterPack(context) { - if (context.electronPlatformName === 'linux') { - context.targets.forEach(async (target) => { - if (!['appimage', 'snap'].includes(target.name.toLowerCase())) { - const result = await spawn('chmod', [SETUID_PERMISSIONS, path.join(context.appOutDir, 'chrome-sandbox')]); - if (result.code !== 0) { - throw new Error( - `Failed to set proper permissions for linux arch on ${target.name}`, - ); - } - } - }); - } -}; \ No newline at end of file + if (context.electronPlatformName === 'linux') { + context.targets.forEach(async (target) => { + if (!['appimage', 'snap'].includes(target.name.toLowerCase())) { + const result = await spawn('chmod', [SETUID_PERMISSIONS, path.join(context.appOutDir, 'chrome-sandbox')]); + if (result.code !== 0) { + throw new Error( + `Failed to set proper permissions for linux arch on ${target.name}`, + ); + } + } + }); + } +}; diff --git a/scripts/check_build_config.js b/scripts/check_build_config.js index 8702c69f..98cf5395 100644 --- a/scripts/check_build_config.js +++ b/scripts/check_build_config.js @@ -4,16 +4,16 @@ const buildConfig = require('../src/common/config/buildConfig'); function validateBuildConfig(config) { - if (config.enableServerManagement === false && config.defaultTeams && config.defaultTeams.length === 0) { - return { - result: false, - message: `Specify at least one server for "defaultTeams" in buildConfig.js when "enableServerManagement is set to false.\n${JSON.stringify(config, null, 2)}`, - }; - } - return {result: true}; + if (config.enableServerManagement === false && config.defaultTeams && config.defaultTeams.length === 0) { + return { + result: false, + message: `Specify at least one server for "defaultTeams" in buildConfig.js when "enableServerManagement is set to false.\n${JSON.stringify(config, null, 2)}`, + }; + } + return {result: true}; } const ret = validateBuildConfig(buildConfig); if (ret.result === false) { - throw new Error(ret.message); + throw new Error(ret.message); } diff --git a/scripts/cp_artifacts.sh b/scripts/cp_artifacts.sh index 1ded8583..242a8249 100644 --- a/scripts/cp_artifacts.sh +++ b/scripts/cp_artifacts.sh @@ -20,12 +20,12 @@ if [[ -f "${SRC}/mattermost-desktop-${VERSION}-win-x64.zip" ]]; then cp "${SRC}/mattermost-desktop-${VERSION}-win-x64.zip" "${DEST}/mattermost-desktop-${VERSION}-win64.zip" SOMETHING_COPIED=$((SOMETHING_COPIED + 2)) fi -# We are not supplying this since we supply the msi -# if [[ -f "${SRC}/mattermost-desktop-setup-${VERSION}-win.exe" ]]; then -# echo -e "Copying win-no-arch\n" -# cp "${SRC}/mattermost-desktop-setup-${VERSION}-win.exe" "${DEST}/" -# SOMETHING_COPIED=$((SOMETHING_COPIED + 4)) -# fi + +if [[ ${MM_WIN_INSTALLERS-0} -eq 1 && -f "${SRC}/mattermost-desktop-setup-${VERSION}-win.exe" ]]; then + echo -e "Copying win-no-arch\n" + cp "${SRC}/mattermost-desktop-setup-${VERSION}-win.exe" "${DEST}/" + SOMETHING_COPIED=$((SOMETHING_COPIED + 4)) +fi if [[ -f "${SRC}/mattermost-desktop-${VERSION}-mac.zip" ]]; then echo -e "Copying mac\n" cp "${SRC}"/mattermost-desktop-*-mac.* "${DEST}/" diff --git a/scripts/extract_dict.js b/scripts/extract_dict.js index 909828b7..6714d08b 100644 --- a/scripts/extract_dict.js +++ b/scripts/extract_dict.js @@ -12,13 +12,13 @@ const {path7za} = require('7zip-bin'); const cwd = process.argv[2]; spawn(path7za, ['e', '-y', '*.zip'], { - cwd, - stdio: 'inherit', + cwd, + stdio: 'inherit', }).on('error', (err) => { - console.error(err); - process.exit(1); + console.error(err); + process.exit(1); }).on('close', (code) => { - process.exit(code); + process.exit(code); }); /* eslint-enable no-process-exit */ diff --git a/scripts/manipulate_windows_zip.js b/scripts/manipulate_windows_zip.js index bc7ff948..0ae264ec 100644 --- a/scripts/manipulate_windows_zip.js +++ b/scripts/manipulate_windows_zip.js @@ -4,27 +4,28 @@ 'use strict'; const spawnSync = require('child_process').spawnSync; + const path = require('path'); const path7za = require('7zip-bin').path7za; -const pkg = require('../src/package.json'); +const pkg = require('../package.json'); const appVersion = pkg.version; const name = pkg.name; function disableInstallUpdate(zipPath) { - const zipFullPath = path.resolve(__dirname, '..', zipPath); - const appUpdaterConfigFile = 'app-updater-config.json'; + const zipFullPath = path.resolve(__dirname, '..', zipPath); + const appUpdaterConfigFile = 'app-updater-config.json'; - const addResult = spawnSync(path7za, ['a', zipFullPath, appUpdaterConfigFile], {cwd: 'resources/windows'}); - if (addResult.status !== 0) { - throw new Error(`7za a returned non-zero exit code for ${zipPath}`); - } + const addResult = spawnSync(path7za, ['a', zipFullPath, appUpdaterConfigFile], {cwd: 'resources/windows'}); + if (addResult.status !== 0) { + throw new Error(`7za a returned non-zero exit code for ${zipPath}`); + } - const renameResult = spawnSync(path7za, ['rn', zipFullPath, appUpdaterConfigFile, `resources/${appUpdaterConfigFile}`]); - if (renameResult.status !== 0) { - throw new Error(`7za rn returned non-zero exit code for ${zipPath}`); - } + const renameResult = spawnSync(path7za, ['rn', zipFullPath, appUpdaterConfigFile, `resources/${appUpdaterConfigFile}`]); + if (renameResult.status !== 0) { + throw new Error(`7za rn returned non-zero exit code for ${zipPath}`); + } } console.log('Manipulating 64-bit zip...'); diff --git a/scripts/notarize.js b/scripts/notarize.js index cd31265e..9654b4a9 100644 --- a/scripts/notarize.js +++ b/scripts/notarize.js @@ -8,22 +8,22 @@ const {notarize} = require('electron-notarize'); const config = require('../electron-builder.json'); exports.default = async function notarizing(context) { - const {electronPlatformName, appOutDir} = context; - if (electronPlatformName !== 'darwin' || process.platform !== 'darwin') { - return; - } + const {electronPlatformName, appOutDir} = context; + if (electronPlatformName !== 'darwin' || process.platform !== 'darwin') { + return; + } - const appName = context.packager.appInfo.productFilename; - if (typeof process.env.APPLEID === 'undefined') { - console.log('skipping notarization, remember to setup environment variables for APPLEID and APPLEIDPASS if you want to notarize'); - return; - } - await notarize({ + const appName = context.packager.appInfo.productFilename; + if (typeof process.env.APPLEID === 'undefined') { + console.log('skipping notarization, remember to setup environment variables for APPLEID and APPLEIDPASS if you want to notarize'); + return; + } + await notarize({ - // should we change it to appBundleId: 'com.mattermost.desktop', - appBundleId: config.appId, - appPath: `${appOutDir}/${appName}.app`, - appleId: process.env.APPLEID, - appleIdPassword: process.env.APPLEIDPASS, - }); -}; \ No newline at end of file + // should we change it to appBundleId: 'com.mattermost.desktop', + appBundleId: config.appId, + appPath: `${appOutDir}/${appName}.app`, + appleId: process.env.APPLEID, + appleIdPassword: process.env.APPLEIDPASS, + }); +}; diff --git a/scripts/release.sh b/scripts/release.sh index ff7b75d2..e776434f 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -26,12 +26,8 @@ function write_package_version { jq ".version = \"${1}\"" ./package.json > "${temp_file}" && mv "${temp_file}" ./package.json temp_file="$(mktemp -t package-lock.json)" jq ".version = \"${1}\"" ./package-lock.json > "${temp_file}" && mv "${temp_file}" ./package-lock.json - temp_file="$(mktemp -t src-package.json)" - jq ".version = \"${1}\"" ./src/package.json > "${temp_file}" && mv "${temp_file}" ./src/package.json - temp_file="$(mktemp -t src-package-lock.json)" - jq ".version = \"${1}\"" ./src/package-lock.json > "${temp_file}" && mv "${temp_file}" ./src/package-lock.json - git add ./package.json ./package-lock.json ./src/package.json ./src/package-lock.json + git add ./package.json ./package-lock.json git commit -qm "Bump to version ${1}" } diff --git a/scripts/watch_main_and_preload.js b/scripts/watch_main_and_preload.js index ed06a796..9bd7acbf 100644 --- a/scripts/watch_main_and_preload.js +++ b/scripts/watch_main_and_preload.js @@ -2,7 +2,7 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. const webpack = require('webpack'); -const electron = require('electron-connect').server.create({path: 'src'}); +const electron = require('electron-connect').server.create({path: 'dist/'}); const mainConfig = require('../webpack.config.main.js'); const rendererConfig = require('../webpack.config.renderer.js'); @@ -11,28 +11,21 @@ let started = false; const mainCompiler = webpack(mainConfig); mainCompiler.watch({}, (err, stats) => { - process.stdout.write(stats.toString({colors: true})); - process.stdout.write('\n'); - if (!stats.hasErrors()) { - if (started) { - electron.restart(); - } else { - electron.start(); - started = true; + process.stdout.write(stats.toString({colors: true})); + process.stdout.write('\n'); + if (!stats.hasErrors()) { + if (started) { + electron.restart(); + } else { + electron.start(); + started = true; + } } - } }); -for (const key in rendererConfig.entry) { - if (!key.startsWith('webview/')) { - if ({}.hasOwnProperty.call(rendererConfig.entry, key)) { - delete rendererConfig.entry[key]; - } - } -} const preloadCompiler = webpack(rendererConfig); preloadCompiler.watch({}, (err) => { - if (err) { - console.log(err); - } + if (err) { + console.log(err); + } }); diff --git a/src/assets/fonts/fontawesome-webfont.eot b/src/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/src/assets/fonts/fontawesome-webfont.eot differ diff --git a/src/assets/fonts/fontawesome-webfont.ttf b/src/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/src/assets/fonts/fontawesome-webfont.ttf differ diff --git a/src/assets/fonts/fontawesome-webfont.woff b/src/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/src/assets/fonts/fontawesome-webfont.woff differ diff --git a/src/assets/fonts/fontawesome-webfont.woff2 b/src/assets/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/src/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/src/browser/components/AutoSaveIndicator.jsx b/src/browser/components/AutoSaveIndicator.jsx deleted file mode 100644 index 278110a7..00000000 --- a/src/browser/components/AutoSaveIndicator.jsx +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; -import {Alert} from 'react-bootstrap'; - -const baseClassName = 'AutoSaveIndicator'; -const leaveClassName = `${baseClassName}-Leave`; - -const SAVING_STATE_SAVING = 'saving'; -const SAVING_STATE_SAVED = 'saved'; -const SAVING_STATE_ERROR = 'error'; -const SAVING_STATE_DONE = 'done'; - -function getClassNameAndMessage(savingState, errorMessage) { - switch (savingState) { - case SAVING_STATE_SAVING: - return {className: baseClassName, message: 'Saving...'}; - case SAVING_STATE_SAVED: - return {className: baseClassName, message: 'Saved'}; - case SAVING_STATE_ERROR: - return {className: `${baseClassName}`, message: errorMessage}; - case SAVING_STATE_DONE: - return {className: `${baseClassName} ${leaveClassName}`, message: 'Saved'}; - default: - return {className: `${baseClassName} ${leaveClassName}`, message: ''}; - } -} - -export default function AutoSaveIndicator(props) { - const {savingState, errorMessage, ...rest} = props; - const {className, message} = getClassNameAndMessage(savingState, errorMessage); - return ( - - {message} - - ); -} - -AutoSaveIndicator.propTypes = { - savingState: PropTypes.string.isRequired, - errorMessage: PropTypes.string, -}; - -Object.assign(AutoSaveIndicator, { - SAVING_STATE_SAVING, - SAVING_STATE_SAVED, - SAVING_STATE_ERROR, - SAVING_STATE_DONE, -}); diff --git a/src/browser/components/Button/Button.stories.jsx b/src/browser/components/Button/Button.stories.jsx deleted file mode 100644 index 1f935c83..00000000 --- a/src/browser/components/Button/Button.stories.jsx +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import {storiesOf} from '@storybook/react'; - -import {action} from '@storybook/addon-actions'; -import {Button, ButtonToolbar} from 'react-bootstrap'; - -storiesOf('Button', module). - add('bsStyle', () => ( - - - - - - - )); diff --git a/src/browser/components/DestructiveConfirmModal.jsx b/src/browser/components/DestructiveConfirmModal.jsx deleted file mode 100644 index 3a1c8af5..00000000 --- a/src/browser/components/DestructiveConfirmModal.jsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; -import {Button, Modal} from 'react-bootstrap'; - -export default function DestructiveConfirmationModal(props) { - const { - title, - body, - acceptLabel, - cancelLabel, - onAccept, - onCancel, - ...rest} = props; - return ( - - - {title} - - {body} - - - - - - ); -} - -DestructiveConfirmationModal.propTypes = { - title: PropTypes.string.isRequired, - body: PropTypes.node.isRequired, - acceptLabel: PropTypes.string.isRequired, - cancelLabel: PropTypes.string.isRequired, - onAccept: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -}; diff --git a/src/browser/components/ErrorView.jsx b/src/browser/components/ErrorView.jsx deleted file mode 100644 index f8d28dcf..00000000 --- a/src/browser/components/ErrorView.jsx +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// ErrorCode: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h - -import React from 'react'; -import PropTypes from 'prop-types'; -import {Grid, Row, Col} from 'react-bootstrap'; -import {shell, remote} from 'electron'; - -export default function ErrorView(props) { - const classNames = ['container', 'ErrorView']; - if (!props.active) { - classNames.push('ErrorView-hidden'); - } - function handleClick(event) { - event.preventDefault(); - shell.openExternal(props.errorInfo.validatedURL); - } - return ( - -
-
- - - -

{`Cannot connect to ${remote.app.name}`}

-
-

{`We're having trouble connecting to ${remote.app.name}. If refreshing this page (Ctrl+R or Command+R) does not work please verify that:`}

-
- -
-
- {props.errorInfo.errorDescription}{' ('} - {props.errorInfo.errorCode }{')'}
- - -
-
-
-
- ); -} - -ErrorView.propTypes = { - errorInfo: PropTypes.object, - id: PropTypes.string, - active: PropTypes.bool, -}; diff --git a/src/browser/components/ExtraBar.jsx b/src/browser/components/ExtraBar.jsx deleted file mode 100644 index 9c9f45dc..00000000 --- a/src/browser/components/ExtraBar.jsx +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import PropTypes from 'prop-types'; -import {Row, Button} from 'react-bootstrap'; - -export default class ExtraBar extends React.Component { - handleBack = () => { - if (this.props.mattermostView) { - this.props.mattermostView.goBack(); - } - } - render() { - let barClass = 'clear-mode'; - if (!this.props.show) { - barClass = 'hidden'; - } else if (this.props.darkMode) { - barClass = 'dark-mode'; - } - - return ( - -
- -
-
- ); - } -} - -ExtraBar.propTypes = { - darkMode: PropTypes.bool, - mattermostView: PropTypes.object, - show: PropTypes.bool, -}; \ No newline at end of file diff --git a/src/browser/components/Finder.jsx b/src/browser/components/Finder.jsx deleted file mode 100644 index f1e62ee2..00000000 --- a/src/browser/components/Finder.jsx +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -// eslint-disable-next-line eslint-comments/disable-enable-pair -/* eslint-disable react/no-set-state */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class Finder extends React.Component { - constructor(props) { - super(props); - this.webview = document.getElementById('mattermostView' + this.props.webviewKey); - this.state = { - foundInPage: false, - searchTxt: '', - }; - } - - componentDidMount() { - this.webview.addEventListener('found-in-page', this.foundInPage); - this.searchInput.focus(); - - // synthetic events are not working all that reliably for touch bar with esc keys - this.searchInput.addEventListener('keyup', this.handleKeyEvent); - } - - componentWillUnmount() { - this.webview.stopFindInPage('clearSelection'); - this.webview.removeEventListener('found-in-page', this.foundInPage); - this.searchInput.removeEventListener('keyup', this.handleKeyEvent); - } - - componentDidUpdate(prevProps) { - if (this.props.focusState && (this.props.focusState !== prevProps.focusState)) { - this.searchInput.focus(); - } - } - - findNext = () => { - this.webview.findInPage(this.state.searchTxt, { - forward: true, - findNext: true, - }); - }; - - find = (keyword) => { - this.webview.stopFindInPage('clearSelection'); - if (keyword) { - this.webview.findInPage(keyword); - } else { - this.setState({ - matches: '0/0', - }); - } - }; - - findPrev = () => { - this.webview.findInPage(this.state.searchTxt, {forward: false, findNext: true}); - } - - searchTxt = (event) => { - this.setState({searchTxt: event.target.value}); - this.find(event.target.value); - } - - handleKeyEvent = (event) => { - if (event.code === 'Escape') { - this.props.close(); - } else if (event.code === 'Enter') { - this.findNext(); - } - } - - foundInPage = (event) => { - const {matches, activeMatchOrdinal} = event.result; - this.setState({ - foundInPage: true, - matches: `${activeMatchOrdinal}/${matches}`, - }); - } - - inputFocus = (e) => { - e.stopPropagation(); - this.props.inputFocus(e, true); - } - - inputBlur = (e) => { - this.props.inputFocus(e, false); - } - - render() { - return ( -
-
-
- { - this.searchInput = input; - }} - /> - {this.state.matches} -
- - - -
-
- ); - } -} - -Finder.propTypes = { - close: PropTypes.func, - webviewKey: PropTypes.number, - focusState: PropTypes.bool, - inputFocus: PropTypes.func, -}; diff --git a/src/browser/components/HoveringURL.jsx b/src/browser/components/HoveringURL.jsx deleted file mode 100644 index d078b3e5..00000000 --- a/src/browser/components/HoveringURL.jsx +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; - -export default function HoveringURL(props) { - return ( -
- {props.targetURL} -
- ); -} - -HoveringURL.propTypes = { - targetURL: PropTypes.string, -}; diff --git a/src/browser/components/LoadingAnimation/LoadingAnimation.jsx b/src/browser/components/LoadingAnimation/LoadingAnimation.jsx deleted file mode 100644 index 01e89c61..00000000 --- a/src/browser/components/LoadingAnimation/LoadingAnimation.jsx +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; - -import useAnimationEnd from '../../hooks/useAnimationEnd.js'; - -import LoadingIcon from './LoadingIcon.jsx'; - -const LOADING_STATE = { - INITIALIZING: 'initializing', // animation graphics are hidden - LOADING: 'loading', // animation graphics fade in and animate - LOADED: 'loaded', // animation graphics fade out - COMPLETE: 'complete', // animation graphics are removed from the DOM -}; - -const ANIMATION_COMPLETION_DELAY = 500; - -/** - * A function component for rendering the animated MM logo loading sequence - * @param {boolean} loading - Prop that indicates whether currently loading or not - * @param {boolean} darkMode - Prop that indicates if dark mode is enabled - * @param {function} onLoadingAnimationComplete - Callback function to update when internal loading animation is complete - */ -function LoadingAnimation({ - loading = false, - darkMode = false, - onLoadAnimationComplete = null} -) { - const loadingIconContainerRef = React.useRef(null); - const [animationState, setAnimationState] = React.useState(LOADING_STATE.INITIALIZING); - const [loadingAnimationComplete, setLoadingAnimationComplete] = React.useState(false); - - React.useEffect(() => { - if (loading) { - setAnimationState(LOADING_STATE.LOADING); - setLoadingAnimationComplete(false); - } - - // in order for the logo animation to fully complete before fading out, the LOADED state is not set until - // both the external loaded prop changes back to false and the internal loading animation is complete - if (!loading && loadingAnimationComplete) { - setAnimationState(LOADING_STATE.LOADED); - } - }, [loading]); - - React.useEffect(() => { - // in order for the logo animation to fully complete before fading out, the LOADED state is not set until - // both the external loaded prop goes back to false and the internal loading animation is complete - if (!loading && loadingAnimationComplete) { - setAnimationState(LOADING_STATE.LOADED); - } - }, [loadingAnimationComplete]); - - // listen for end of the css logo animation sequence - useAnimationEnd(loadingIconContainerRef, () => { - setTimeout(() => { - setLoadingAnimationComplete(true); - }, ANIMATION_COMPLETION_DELAY); - }, 'LoadingAnimation__compass-shrink'); - - // listen for end of final css logo fade/shrink animation sequence - useAnimationEnd(loadingIconContainerRef, () => { - if (onLoadAnimationComplete) { - onLoadAnimationComplete(); - } - setAnimationState(LOADING_STATE.COMPLETE); - }, 'LoadingAnimation__shrink'); - - return ( -
- -
- ); -} - -LoadingAnimation.propTypes = { - loading: PropTypes.bool, - darkMode: PropTypes.bool, - onLoadAnimationComplete: PropTypes.func, -}; - -export default LoadingAnimation; diff --git a/src/browser/components/LoadingAnimation/LoadingIcon.jsx b/src/browser/components/LoadingAnimation/LoadingIcon.jsx deleted file mode 100644 index 2b279e5b..00000000 --- a/src/browser/components/LoadingAnimation/LoadingIcon.jsx +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -/** - * A function component for inlining SVG code for animation logo loader - */ -function LoadingAnimation() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} - -export default LoadingAnimation; diff --git a/src/browser/components/LoadingScreen.jsx b/src/browser/components/LoadingScreen.jsx deleted file mode 100644 index cddd5047..00000000 --- a/src/browser/components/LoadingScreen.jsx +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import classNames from 'classnames'; -import PropTypes from 'prop-types'; - -import useTransitionEnd from '../hooks/useTransitionEnd.js'; - -import LoadingAnimation from './LoadingAnimation'; - -/** - * A function component for rendering the desktop app loading screen - * @param {boolean} loading - Prop that indicates whether currently loading or not - * @param {boolean} darkMode - Prop that indicates if dark mode is enabled - */ -function LoadingScreen({loading = false, darkMode = false}) { - const loadingScreenRef = React.useRef(null); - - const [loadingIsComplete, setLoadingIsComplete] = React.useState(true); - const [loadAnimationIsComplete, setLoadAnimationIsComplete] = React.useState(true); - const [fadeOutIsComplete, setFadeOutIsComplete] = React.useState(true); - - React.useEffect(() => { - // reset internal state if loading restarts - if (loading) { - resetState(); - } else { - setLoadingIsComplete(true); - } - }, [loading]); - - function handleLoadAnimationComplete() { - setLoadAnimationIsComplete(true); - } - - useTransitionEnd(loadingScreenRef, React.useCallback(() => { - setFadeOutIsComplete(true); - }), ['opacity']); - - function loadingInProgress() { - return !(loadingIsComplete && loadAnimationIsComplete && fadeOutIsComplete); - } - - function resetState() { - setLoadingIsComplete(false); - setLoadAnimationIsComplete(false); - setFadeOutIsComplete(false); - } - - const loadingScreen = ( -
- -
- ); - - return loadingInProgress() ? loadingScreen : null; -} - -LoadingScreen.propTypes = { - loading: PropTypes.bool, - darkMode: PropTypes.bool, -}; - -export default LoadingScreen; diff --git a/src/browser/components/LoginModal.jsx b/src/browser/components/LoginModal.jsx deleted file mode 100644 index 49f7596b..00000000 --- a/src/browser/components/LoginModal.jsx +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; -import {Button, Col, ControlLabel, Form, FormGroup, FormControl, Modal} from 'react-bootstrap'; - -export default class LoginModal extends React.Component { - constructor(props) { - super(props); - this.state = { - username: '', - password: '', - }; - } - - handleSubmit = (event) => { - event.preventDefault(); - this.props.onLogin(this.props.request, this.state.username, this.state.password); - this.setState({ - username: '', - password: '', - }); - } - - handleCancel = (event) => { - event.preventDefault(); - this.props.onCancel(this.props.request); - this.setState({ - username: '', - password: '', - }); - } - - setUsername = (e) => { - this.setState({username: e.target.value}); - } - - setPassword = (e) => { - this.setState({password: e.target.value}); - } - - render() { - let theServer = ''; - if (!this.props.show) { - theServer = ''; - } else if (this.props.authInfo.isProxy) { - theServer = `The proxy ${this.props.authInfo.host}:${this.props.authInfo.port}`; - } else { - theServer = `The server ${this.props.authServerURL}`; - } - const message = `${theServer} requires a username and password.`; - return ( - - - {'Authentication Required'} - - -

- { message } -

-
- - {'User Name'} - - { - e.stopPropagation(); - }} - /> - - - - {'Password'} - - { - e.stopPropagation(); - }} - /> - - - - -
- - { ' ' } - -
- -
-
-
-
- ); - } -} - -LoginModal.propTypes = { - authInfo: PropTypes.object, - authServerURL: PropTypes.string, - onCancel: PropTypes.func, - onLogin: PropTypes.func, - request: PropTypes.object, - show: PropTypes.bool, -}; diff --git a/src/browser/components/MainPage.jsx b/src/browser/components/MainPage.jsx deleted file mode 100644 index 8d632383..00000000 --- a/src/browser/components/MainPage.jsx +++ /dev/null @@ -1,912 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// This files uses setState(). -/* eslint-disable react/no-set-state */ - -import os from 'os'; - -import React, {Fragment} from 'react'; -import PropTypes from 'prop-types'; -import {CSSTransition, TransitionGroup} from 'react-transition-group'; -import {Grid, Row} from 'react-bootstrap'; -import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'; - -import {ipcRenderer, remote, shell} from 'electron'; - -import Utils from '../../utils/util'; -import urlUtils from '../../utils/url'; -import contextmenu from '../js/contextMenu'; - -import restoreButton from '../../assets/titlebar/chrome-restore.svg'; -import maximizeButton from '../../assets/titlebar/chrome-maximize.svg'; -import minimizeButton from '../../assets/titlebar/chrome-minimize.svg'; -import closeButton from '../../assets/titlebar/chrome-close.svg'; - -import LoginModal from './LoginModal.jsx'; -import MattermostView from './MattermostView.jsx'; -import TabBar from './TabBar.jsx'; -import HoveringURL from './HoveringURL.jsx'; -import Finder from './Finder.jsx'; -import NewTeamModal from './NewTeamModal.jsx'; -import SelectCertificateModal from './SelectCertificateModal.jsx'; -import PermissionModal from './PermissionModal.jsx'; -import ExtraBar from './ExtraBar.jsx'; - -export default class MainPage extends React.Component { - constructor(props) { - super(props); - - let key = this.props.teams.findIndex((team) => team.order === this.props.initialIndex); - if (this.props.deeplinkingUrl !== null) { - const parsedDeeplink = this.parseDeeplinkURL(this.props.deeplinkingUrl); - if (parsedDeeplink) { - key = parsedDeeplink.teamIndex; - } - } - - this.topBar = React.createRef(); - - this.state = { - key, - sessionsExpired: new Array(this.props.teams.length), - unreadCounts: new Array(this.props.teams.length), - mentionCounts: new Array(this.props.teams.length), - unreadAtActive: new Array(this.props.teams.length), - mentionAtActiveCounts: new Array(this.props.teams.length), - loginQueue: [], - targetURL: '', - certificateRequests: [], - maximized: false, - showNewTeamModal: false, - focusFinder: false, - finderVisible: false, - }; - contextmenu.setup({ - useSpellChecker: this.props.useSpellChecker, - onSelectSpellCheckerLocale: (locale) => { - if (this.props.onSelectSpellCheckerLocale) { - this.props.onSelectSpellCheckerLocale(locale); - } - }, - }); - } - - parseDeeplinkURL(deeplink, teams = this.props.teams) { - if (deeplink && Array.isArray(teams) && teams.length) { - const deeplinkURL = urlUtils.parseURL(deeplink); - let parsedDeeplink = null; - teams.forEach((team, index) => { - const teamURL = urlUtils.parseURL(team.url); - if (deeplinkURL.host === teamURL.host) { - parsedDeeplink = { - teamURL, - teamIndex: index, - originalURL: deeplinkURL, - url: `${teamURL.origin}${deeplinkURL.pathname || '/'}`, - path: deeplinkURL.pathname || '/', - }; - } - }); - return parsedDeeplink; - } - return null; - } - - getTabWebContents(index = this.state.key || 0, teams = this.props.teams) { - const allWebContents = remote.webContents.getAllWebContents(); - - if (this.state.showNewTeamModal) { - const indexURL = '/browser/index.html'; - return allWebContents.find((webContents) => webContents.getURL().includes(indexURL)); - } - - if (!teams || !teams.length || index > teams.length) { - return null; - } - const tabURL = teams[index].url; - if (!tabURL) { - return null; - } - const tab = allWebContents.find((webContents) => webContents.isFocused() && webContents.getURL().includes(this.refs[`mattermostView${index}`].getSrc())); - return tab || remote.webContents.getFocusedWebContents(); - } - - componentDidMount() { - const self = this; - - // Due to a bug in Chrome on macOS, mousemove events from the webview won't register when the webview isn't in focus, - // thus you can't drag tabs unless you're right on the container. - // this makes it so your tab won't get stuck to your cursor no matter where you mouse up - if (process.platform === 'darwin') { - self.topBar.current.addEventListener('mouseleave', () => { - if (event.target === self.topBar.current) { - const upEvent = document.createEvent('MouseEvents'); - upEvent.initMouseEvent('mouseup'); - document.dispatchEvent(upEvent); - } - }); - - // Hack for when it leaves the electron window because apparently mouseleave isn't good enough there... - self.topBar.current.addEventListener('mousemove', () => { - if (event.clientY === 0 || event.clientX === 0 || event.clientX >= window.innerWidth) { - const upEvent = document.createEvent('MouseEvents'); - upEvent.initMouseEvent('mouseup'); - document.dispatchEvent(upEvent); - } - }); - } - - ipcRenderer.on('login-request', (event, request, authInfo) => { - this.loginRequest(event, request, authInfo); - }); - - ipcRenderer.on('select-user-certificate', (_, origin, certificateList) => { - const certificateRequests = self.state.certificateRequests; - certificateRequests.push({ - server: origin, - certificateList, - }); - self.setState({ - certificateRequests, - }); - if (certificateRequests.length === 1) { - self.switchToTabForCertificateRequest(origin); - } - }); - - // can't switch tabs sequentially for some reason... - ipcRenderer.on('switch-tab', (event, key) => { - const nextIndex = this.props.teams.findIndex((team) => team.order === key); - this.handleSelect(nextIndex); - }); - ipcRenderer.on('select-next-tab', () => { - const currentOrder = this.props.teams[this.state.key].order; - const nextOrder = ((currentOrder + 1) % this.props.teams.length); - const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder); - this.handleSelect(nextIndex); - }); - - ipcRenderer.on('select-previous-tab', () => { - const currentOrder = this.props.teams[this.state.key].order; - - // js modulo operator returns a negative number if result is negative, so we have to ensure it's positive - const nextOrder = ((this.props.teams.length + (currentOrder - 1)) % this.props.teams.length); - const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder); - this.handleSelect(nextIndex); - }); - - // reload the activated tab - ipcRenderer.on('reload-tab', () => { - this.refs[`mattermostView${this.state.key}`].reload(); - }); - ipcRenderer.on('clear-cache-and-reload-tab', () => { - this.refs[`mattermostView${this.state.key}`].clearCacheAndReload(); - }); - ipcRenderer.on('download-complete', this.showDownloadCompleteNotification); - - const currentWindow = remote.getCurrentWindow(); - currentWindow.on('focus', self.focusListener); - currentWindow.on('blur', self.blurListener); - window.addEventListener('beforeunload', () => { - currentWindow.removeListener('focus', self.focusListener); - }); - - if (currentWindow.isMaximized()) { - self.setState({maximized: true}); - } - currentWindow.on('maximize', this.handleMaximizeState); - currentWindow.on('unmaximize', this.handleMaximizeState); - - if (currentWindow.isFullScreen()) { - self.setState({fullScreen: true}); - } - currentWindow.on('enter-full-screen', this.handleFullScreenState); - currentWindow.on('leave-full-screen', this.handleFullScreenState); - - // https://github.com/mattermost/desktop/pull/371#issuecomment-263072803 - currentWindow.webContents.on('devtools-closed', () => { - self.focusListener(); - }); - - ipcRenderer.on('open-devtool', () => { - document.getElementById(`mattermostView${self.state.key}`).openDevTools(); - }); - - ipcRenderer.on('zoom-in', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - if (activeTabWebContents.zoomLevel >= 9) { - return; - } - activeTabWebContents.zoomLevel += 1; - }); - - ipcRenderer.on('zoom-out', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - if (activeTabWebContents.zoomLevel <= -8) { - return; - } - activeTabWebContents.zoomLevel -= 1; - }); - - ipcRenderer.on('zoom-reset', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.zoomLevel = 0; - }); - - ipcRenderer.on('undo', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.undo(); - }); - - ipcRenderer.on('redo', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.redo(); - }); - - ipcRenderer.on('cut', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.cut(); - }); - - ipcRenderer.on('copy', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.copy(); - }); - - ipcRenderer.on('paste', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.paste(); - }); - - ipcRenderer.on('paste-and-match', () => { - const activeTabWebContents = this.getTabWebContents(this.state.key); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.pasteAndMatchStyle(); - }); - - //goBack and goForward - ipcRenderer.on('go-back', () => { - const mattermost = self.refs[`mattermostView${self.state.key}`]; - if (mattermost.canGoBack()) { - mattermost.goBack(); - } - }); - - ipcRenderer.on('go-forward', () => { - const mattermost = self.refs[`mattermostView${self.state.key}`]; - if (mattermost.canGoForward()) { - mattermost.goForward(); - } - }); - - ipcRenderer.on('add-server', () => { - this.addServer(); - }); - - ipcRenderer.on('focus-on-webview', () => { - this.focusOnWebView(); - }); - - ipcRenderer.on('protocol-deeplink', (event, deepLinkUrl) => { - const parsedDeeplink = this.parseDeeplinkURL(deepLinkUrl); - if (parsedDeeplink) { - if (this.state.key !== parsedDeeplink.teamIndex) { - this.handleSelect(parsedDeeplink.teamIndex); - } - self.refs[`mattermostView${parsedDeeplink.teamIndex}`].handleDeepLink(parsedDeeplink.path); - } - }); - - ipcRenderer.on('toggle-find', () => { - this.activateFinder(true); - }); - - if (process.platform === 'darwin') { - self.setState({ - isDarkMode: remote.nativeTheme.shouldUseDarkColors, - }); - remote.systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => { - self.setState({ - isDarkMode: remote.nativeTheme.shouldUseDarkColors, - }); - }); - } else { - self.setState({ - isDarkMode: this.props.getDarkMode(), - }); - - ipcRenderer.on('set-dark-mode', () => { - this.setDarkMode(); - }); - - this.threeDotMenu = React.createRef(); - ipcRenderer.on('focus-three-dot-menu', () => { - if (this.threeDotMenu.current) { - this.threeDotMenu.current.focus(); - } - }); - } - } - - focusListener = () => { - if (this.state.showNewTeamModal && this.inputRef && this.inputRef.current) { - this.inputRef.current.focus(); - } else if (!(this.state.finderVisible && this.state.focusFinder)) { - this.handleOnTeamFocused(this.state.key); - this.refs[`mattermostView${this.state.key}`].focusOnWebView(); - } - this.setState({unfocused: false}); - } - - blurListener = () => { - this.setState({unfocused: true}); - } - loginRequest = (event, request, authInfo) => { - const loginQueue = this.state.loginQueue; - loginQueue.push({ - request, - authInfo, - }); - this.setState({ - loginRequired: true, - loginQueue, - }); - }; - - componentDidUpdate(prevProps, prevState) { - if (prevState.key !== this.state.key) { // i.e. When tab has been changed - this.refs[`mattermostView${this.state.key}`].focusOnWebView(); - } - } - - switchToTabForCertificateRequest = (origin) => { - // origin is server name + port, if the port doesn't match the protocol, it is kept by URL - const originURL = urlUtils.parseURL(`http://${origin.split(':')[0]}`); - const secureOriginURL = urlUtils.parseURL(`https://${origin.split(':')[0]}`); - - const key = this.props.teams.findIndex((team) => { - const parsedURL = urlUtils.parseURL(team.url); - return (parsedURL.origin === originURL.origin) || (parsedURL.origin === secureOriginURL.origin); - }); - this.handleSelect(key); - }; - - handleInterTeamLink = (linkUrl) => { - const selectedTeam = urlUtils.getServer(linkUrl, this.props.teams); - if (!selectedTeam) { - return; - } - this.refs[`mattermostView${selectedTeam.index}`].handleDeepLink(linkUrl.href); - this.setState({key: selectedTeam.index}); - } - - handleMaximizeState = () => { - const win = remote.getCurrentWindow(); - this.setState({maximized: win.isMaximized()}); - } - - handleFullScreenState = () => { - const win = remote.getCurrentWindow(); - this.setState({fullScreen: win.isFullScreen()}); - } - - handleSelect = (key) => { - const newKey = (this.props.teams.length + key) % this.props.teams.length; - this.setState({ - key: newKey, - finderVisible: false, - }); - const webview = document.getElementById('mattermostView' + newKey); - ipcRenderer.send('update-title', { - title: webview.getTitle(), - }); - window.focus(); - webview.focus(); - this.handleOnTeamFocused(newKey); - } - - handleDragAndDrop = (dropResult) => { - const {removedIndex, addedIndex} = dropResult; - if (removedIndex !== addedIndex) { - const teamIndex = this.props.moveTabs(removedIndex, addedIndex < this.props.teams.length ? addedIndex : this.props.teams.length - 1); - this.handleSelect(teamIndex); - } - } - - handleBadgeChange = (index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) => { - const sessionsExpired = this.state.sessionsExpired; - const unreadCounts = this.state.unreadCounts; - const mentionCounts = this.state.mentionCounts; - const unreadAtActive = this.state.unreadAtActive; - const mentionAtActiveCounts = this.state.mentionAtActiveCounts; - sessionsExpired[index] = sessionExpired; - unreadCounts[index] = unreadCount; - mentionCounts[index] = mentionCount; - - // Never turn on the unreadAtActive flag at current focused tab. - if (this.state.key !== index || !remote.getCurrentWindow().isFocused()) { - unreadAtActive[index] = unreadAtActive[index] || isUnread; - if (isMentioned) { - mentionAtActiveCounts[index]++; - } - } - this.setState({ - sessionsExpired, - unreadCounts, - mentionCounts, - unreadAtActive, - mentionAtActiveCounts, - }); - this.handleBadgesChange(); - } - - markReadAtActive = (index) => { - const unreadAtActive = this.state.unreadAtActive; - const mentionAtActiveCounts = this.state.mentionAtActiveCounts; - unreadAtActive[index] = false; - mentionAtActiveCounts[index] = 0; - this.setState({ - unreadAtActive, - mentionAtActiveCounts, - }); - this.handleBadgesChange(); - } - - handleBadgesChange = () => { - if (this.props.onBadgeChange) { - const someSessionsExpired = this.state.sessionsExpired.some((sessionExpired) => sessionExpired); - - let allUnreadCount = this.state.unreadCounts.reduce((prev, curr) => { - return prev + curr; - }, 0); - this.state.unreadAtActive.forEach((state) => { - if (state) { - allUnreadCount += 1; - } - }); - - let allMentionCount = this.state.mentionCounts.reduce((prev, curr) => { - return prev + curr; - }, 0); - this.state.mentionAtActiveCounts.forEach((count) => { - allMentionCount += count; - }); - - this.props.onBadgeChange(someSessionsExpired, allUnreadCount, allMentionCount); - } - } - - handleOnTeamFocused = (index) => { - // Turn off the flag to indicate whether unread message of active channel contains at current tab. - this.markReadAtActive(index); - } - - handleLogin = (request, username, password) => { - ipcRenderer.send('login-credentials', request, username, password); - const loginQueue = this.state.loginQueue; - loginQueue.shift(); - this.setState({loginQueue}); - } - - handleLoginCancel = (request) => { - ipcRenderer.send('login-cancel', request); - - const loginQueue = this.state.loginQueue; - loginQueue.shift(); - this.setState({loginQueue}); - } - - handleTargetURLChange = (targetURL) => { - clearTimeout(this.targetURLDisappearTimeout); - if (targetURL === '' || this.parseDeeplinkURL(targetURL, [this.props.teams[this.state.key]])) { // Do not show URL for internal links on current team - // set delay to avoid momentary disappearance when hovering over multiple links - this.targetURLDisappearTimeout = setTimeout(() => { - this.setState({targetURL: ''}); - }, 500); - } else { - this.setState({targetURL}); - } - } - - handleClose = (e) => { - e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back. - const win = remote.getCurrentWindow(); - win.close(); - } - - handleMinimize = (e) => { - e.stopPropagation(); - const win = remote.getCurrentWindow(); - win.minimize(); - } - - handleMaximize = (e) => { - e.stopPropagation(); - const win = remote.getCurrentWindow(); - win.maximize(); - } - - handleRestore = () => { - const win = remote.getCurrentWindow(); - win.restore(); - } - - openMenu = () => { - // @eslint-ignore - this.threeDotMenu.current.blur(); - this.props.openMenu(); - } - - handleDoubleClick = () => { - if (process.platform === 'darwin') { - const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string'); - const win = remote.getCurrentWindow(); - if (doubleClickAction === 'Minimize') { - win.minimize(); - } else if (!win.isMaximized()) { - win.maximize(); - } else if (win.isMaximized()) { - win.unmaximize(); - } - } - } - - addServer = () => { - this.setState({ - showNewTeamModal: true, - }); - } - - focusOnWebView = () => { - this.refs[`mattermostView${this.state.key}`].focusOnWebView(); - } - - activateFinder = () => { - this.setState({ - finderVisible: true, - focusFinder: true, - }); - } - - closeFinder = () => { - this.setState({ - finderVisible: false, - focusFinder: false, - }); - } - - inputFocus = (e, focus) => { - this.setState({ - focusFinder: focus, - }); - } - - handleSelectCertificate = (certificate) => { - const certificateRequests = this.state.certificateRequests; - const current = certificateRequests.shift(); - this.setState({certificateRequests}); - ipcRenderer.send('selected-client-certificate', current.server, certificate); - if (certificateRequests.length > 0) { - this.switchToTabForCertificateRequest(certificateRequests[0].server); - } - } - handleCancelCertificate = () => { - const certificateRequests = this.state.certificateRequests; - const current = certificateRequests.shift(); - this.setState({certificateRequests}); - ipcRenderer.send('selected-client-certificate', current.server); - if (certificateRequests.length > 0) { - this.switchToTabForCertificateRequest(certificateRequests[0].server); - } - }; - - showDownloadCompleteNotification = async (event, item) => { - const title = process.platform === 'win32' ? item.serverInfo.name : 'Download Complete'; - const notificationBody = process.platform === 'win32' ? `Download Complete \n ${item.fileName}` : item.fileName; - - await Utils.dispatchNotification(title, notificationBody, false, {}, () => { - shell.showItemInFolder(item.path.normalize()); - }); - } - - setDarkMode() { - this.setState({ - isDarkMode: this.props.setDarkMode(), - }); - } - setInputRef = (ref) => { - this.inputRef = ref; - } - - showExtraBar = () => { - const ref = this.refs[`mattermostView${this.state.key}`]; - if (typeof ref !== 'undefined') { - return !urlUtils.isTeamUrl(this.props.teams[this.state.key].url, ref.getSrc()); - } - return false; - } - - render() { - const self = this; - const tabsRow = ( - - ); - - let topBarClassName = 'topBar'; - if (process.platform === 'darwin') { - topBarClassName += ' macOS'; - } - if (this.state.isDarkMode) { - topBarClassName += ' darkMode'; - } - if (this.state.fullScreen) { - topBarClassName += ' fullScreen'; - } - - let maxButton; - if (this.state.maximized) { - maxButton = ( -
- -
- ); - } else { - maxButton = ( -
- -
- ); - } - - let overlayGradient; - if (process.platform !== 'darwin') { - overlayGradient = ( - - ); - } - - let titleBarButtons; - if (os.platform() === 'win32' && os.release().startsWith('10')) { - titleBarButtons = ( - -
- -
- {maxButton} -
- -
-
- ); - } - - const topRow = ( - -
- - {tabsRow} - {overlayGradient} - {titleBarButtons} -
-
- ); - - const views = this.props.teams.map((team, index) => { - function handleBadgeChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) { - self.handleBadgeChange(index, sessionExpired, unreadCount, mentionCount, isUnread, isMentioned); - } - function handleNotificationClick() { - self.handleSelect(index); - } - const id = 'mattermostView' + index; - const isActive = self.state.key === index; - - let teamUrl = team.url; - - if (this.props.deeplinkingUrl) { - const parsedDeeplink = this.parseDeeplinkURL(this.props.deeplinkingUrl, [team]); - if (parsedDeeplink) { - teamUrl = parsedDeeplink.url; - } - } - - return ( - ); - }); - - const viewsRow = ( - - - - {views} - - ); - - let request = null; - let authServerURL = null; - let authInfo = null; - if (this.state.loginQueue.length !== 0) { - request = this.state.loginQueue[0].request; - const tmpURL = urlUtils.parseURL(this.state.loginQueue[0].request.url); - authServerURL = tmpURL.origin; - authInfo = this.state.loginQueue[0].authInfo; - } - const modal = ( - { - this.setState({ - showNewTeamModal: false, - }); - }} - onSave={(newTeam) => { - this.props.localTeams.push(newTeam); - this.props.onTeamConfigChange(this.props.localTeams, () => { - self.setState({ - showNewTeamModal: false, - key: this.props.teams.length - 1, - }); - }); - }} - /> - ); - return ( -
- - - - - { topRow } - { viewsRow } - { this.state.finderVisible ? ( - - ) : null} - - - { (this.state.targetURL === '') ? - null : - - - - } - -
- { modal } -
-
- ); - } -} - -MainPage.propTypes = { - onBadgeChange: PropTypes.func.isRequired, - teams: PropTypes.array.isRequired, - localTeams: PropTypes.array.isRequired, - onTeamConfigChange: PropTypes.func.isRequired, - initialIndex: PropTypes.number.isRequired, - useSpellChecker: PropTypes.bool.isRequired, - onSelectSpellCheckerLocale: PropTypes.func.isRequired, - deeplinkingUrl: PropTypes.string, - showAddServerButton: PropTypes.bool.isRequired, - getDarkMode: PropTypes.func.isRequired, - setDarkMode: PropTypes.func.isRequired, - moveTabs: PropTypes.func.isRequired, - openMenu: PropTypes.func.isRequired, -}; - -/* eslint-enable react/no-set-state */ diff --git a/src/browser/components/MattermostView.jsx b/src/browser/components/MattermostView.jsx deleted file mode 100644 index b7d999bc..00000000 --- a/src/browser/components/MattermostView.jsx +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// This file uses setState(). -/* eslint-disable react/no-set-state */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import {ipcRenderer, remote, shell} from 'electron'; -import classNames from 'classnames'; - -import contextMenu from '../js/contextMenu'; -import Utils from '../../utils/util'; -import urlUtils from '../../utils/url'; -import {protocols} from '../../../electron-builder.json'; -const scheme = protocols[0].schemes[0]; - -import ErrorView from './ErrorView.jsx'; -import LoadingScreen from './LoadingScreen.jsx'; - -const preloadJS = `file://${remote.app.getAppPath()}/browser/webview/mattermost_bundle.js`; - -const ERR_NOT_IMPLEMENTED = -11; -const U2F_EXTENSION_URL = 'chrome-extension://kmendfapggjehodndflmmgagdbamhnfd/u2f-comms.html'; -const ERR_USER_ABORTED = -3; -const AUTO_RELOAD_TIMER = 30000; - -export default class MattermostView extends React.Component { - constructor(props) { - super(props); - - this.state = { - errorInfo: null, - isContextMenuAdded: false, - reloadTimeoutID: null, - isWebviewLoaded: false, - basename: '/', - }; - - this.webviewRef = React.createRef(); - } - - handleUnreadCountChange = (sessionExpired, unreadCount, mentionCount, isUnread, isMentioned) => { - if (this.props.onBadgeChange) { - this.props.onBadgeChange(sessionExpired, unreadCount, mentionCount, isUnread, isMentioned); - } - } - - componentDidMount() { - const self = this; - const webview = this.webviewRef.current; - - webview.addEventListener('did-fail-load', (e) => { - console.log(self.props.name, 'webview did-fail-load', e); - if (e.errorCode === ERR_USER_ABORTED) { // An operation was aborted (due to user action). - return; - } - if (e.errorCode === ERR_NOT_IMPLEMENTED && e.validatedURL === U2F_EXTENSION_URL) { - // U2F device is not supported, but the guest page should fall back to PIN code in 2FA. - // https://github.com/mattermost/desktop/issues/708 - return; - } - - self.setState({ - errorInfo: e, - isWebviewLoaded: true, - }); - function reload() { - window.removeEventListener('online', reload); - self.reload(); - } - if (navigator.onLine) { - self.setState({ - reloadTimeoutID: setTimeout(reload, AUTO_RELOAD_TIMER), - }); - } else { - window.addEventListener('online', reload); - } - }); - - // Open link in browserWindow. for example, attached files. - webview.addEventListener('new-window', (e) => { - if (!urlUtils.isValidURI(e.url)) { - return; - } - const currentURL = urlUtils.parseURL(webview.getURL()); - const destURL = urlUtils.parseURL(e.url); - if (destURL.protocol !== 'http:' && destURL.protocol !== 'https:' && destURL.protocol !== `${scheme}:`) { - ipcRenderer.send('confirm-protocol', destURL.protocol, e.url); - return; - } - - if (urlUtils.isInternalURL(destURL, currentURL, this.state.basename)) { - if (destURL.path.match(/^\/api\/v[3-4]\/public\/files\//)) { - ipcRenderer.send('download-url', e.url); - } else if (destURL.path.match(/^\/help\//)) { - // continue to open special case internal urls in default browser - shell.openExternal(e.url); - } else if (urlUtils.isTeamUrl(this.props.src, e.url, true) || urlUtils.isAdminUrl(this.props.src, e.url)) { - e.preventDefault(); - this.webviewRef.current.loadURL(e.url); - } else if (urlUtils.isPluginUrl(this.props.src, e.url)) { - // New window should disable nodeIntegration. - window.open(e.url, remote.app.name, 'nodeIntegration=no, contextIsolation=yes, show=yes'); - } else if (urlUtils.isManagedResource(this.props.src, e.url)) { - e.preventDefault(); - } else { - e.preventDefault(); - shell.openExternal(e.url); - } - } else { - const parsedURL = urlUtils.parseURL(e.url); - const serverURL = urlUtils.getServer(parsedURL, this.props.teams); - if (serverURL !== null && urlUtils.isTeamUrl(serverURL.url, parsedURL)) { - this.props.handleInterTeamLink(parsedURL); - } else { - // if the link is external, use default os' application. - ipcRenderer.send('confirm-protocol', destURL.protocol, e.url); - } - } - }); - - // 'dom-ready' means "content has been loaded" - // So this would be emitted again when reloading a webview - webview.addEventListener('dom-ready', () => { - // webview.openDevTools(); - - // Remove this once https://github.com/electron/electron/issues/14474 is fixed - // - fixes missing cursor bug in electron - // - only apply this focus fix if the current view is active - if (this.props.active) { - webview.blur(); - webview.focus(); - } - if (!this.state.isContextMenuAdded) { - contextMenu.setup({ - window: webview, - useSpellChecker: this.props.useSpellChecker, - onSelectSpellCheckerLocale: (locale) => { - if (this.props.onSelectSpellCheckerLocale) { - this.props.onSelectSpellCheckerLocale(locale); - } - webview.send('set-spellchecker'); - }, - }); - this.setState({isContextMenuAdded: true}); - } - }); - - webview.addEventListener('update-target-url', (event) => { - if (self.props.onTargetURLChange) { - self.props.onTargetURLChange(event.url); - } - }); - - webview.addEventListener('ipc-message', (event) => { - switch (event.channel) { - case 'onGuestInitialized': - self.setState({ - isWebviewLoaded: true, - basename: event.args[0] || '/', - }); - break; - case 'onBadgeChange': { - self.handleUnreadCountChange(...event.args); - break; - } - case 'dispatchNotification': { - const [title, body, channel, teamId, silent, data] = event.args; - Utils.dispatchNotification(title, body, silent, data, () => this.webviewRef.current.send('notification-clicked', {channel, teamId})); - break; - } - case 'onNotificationClick': - self.props.onNotificationClick(); - break; - case 'mouse-move': - this.handleMouseMove(event.args[0]); - break; - case 'mouse-up': - this.handleMouseUp(); - break; - } - }); - - webview.addEventListener('page-title-updated', (event) => { - if (self.props.active) { - ipcRenderer.send('update-title', { - title: event.title, - }); - } - }); - - webview.addEventListener('console-message', (e) => { - const message = `[${this.props.name}] ${e.message}`; - switch (e.level) { - case 0: - console.log(message); - break; - case 1: - console.warn(message); - break; - case 2: - console.error(message); - break; - default: - console.log(message); - break; - } - }); - - // start listening for user status updates from main - ipcRenderer.on('user-activity-update', this.handleUserActivityUpdate); - ipcRenderer.on('exit-fullscreen', this.handleExitFullscreen); - } - - componentWillUnmount() { - // stop listening for user status updates from main - ipcRenderer.removeListener('user-activity-update', this.handleUserActivityUpdate); - ipcRenderer.removeListener('exit-fullscreen', this.handleExitFullscreen); - } - - reload = () => { - clearTimeout(this.state.reloadTimeoutID); - this.setState({ - errorInfo: null, - reloadTimeoutID: null, - isWebviewLoaded: false, - }); - const webview = this.webviewRef.current; - if (webview) { - webview.reload(); - } - } - - clearCacheAndReload = () => { - this.setState({ - errorInfo: null, - }); - const webContents = this.webviewRef.current.getWebContents(); - webContents.session.clearCache().then(webContents.reload); - } - - focusOnWebView = () => { - const webview = this.webviewRef.current; - webview.focus(); - } - - handleMouseMove = (event) => { - const moveEvent = document.createEvent('MouseEvents'); - moveEvent.initMouseEvent('mousemove', null, null, null, null, null, null, event.clientX, event.clientY); - document.dispatchEvent(moveEvent); - } - - handleMouseUp = () => { - const upEvent = document.createEvent('MouseEvents'); - upEvent.initMouseEvent('mouseup'); - document.dispatchEvent(upEvent); - } - - canGoBack = () => { - const webview = this.webviewRef.current; - return webview.getWebContents().canGoBack(); - } - - canGoForward = () => { - const webview = this.webviewRef.current; - return webview.getWebContents().canGoForward(); - } - - goBack = () => { - try { - const webview = this.webviewRef.current; - webview.getWebContents().goBack(); - } catch (e) { - console.log(`Error while trying to go back in history: ${e}`); - this.webview.loadURL(this.props.src); - } - } - - goForward = () => { - const webview = this.webviewRef.current; - webview.getWebContents().goForward(); - } - - getSrc = () => { - const webview = this.webviewRef.current; - return webview.src; - } - - handleDeepLink = (relativeUrl) => { - const webview = this.webviewRef.current; - webview.executeJavaScript( - 'history.pushState(null, null, "' + relativeUrl + '");', - ); - webview.executeJavaScript( - 'dispatchEvent(new PopStateEvent("popstate", null));', - ); - } - - handleUserActivityUpdate = (_, status) => { - // pass user activity update to the webview - this.webviewRef.current.send('user-activity-update', status); - } - - handleExitFullscreen = () => { - // pass exit fullscreen request to the webview - this.webviewRef.current.send('exit-fullscreen'); - } - - render() { - const errorView = this.state.errorInfo ? ( - - ) : null; - - return ( -
- { errorView } - - -
); - } -} - -MattermostView.propTypes = { - name: PropTypes.string, - id: PropTypes.string, - teams: PropTypes.array.isRequired, - withTab: PropTypes.bool, - onTargetURLChange: PropTypes.func, - onBadgeChange: PropTypes.func, - src: PropTypes.string, - active: PropTypes.bool, - useSpellChecker: PropTypes.bool, - onSelectSpellCheckerLocale: PropTypes.func, - handleInterTeamLink: PropTypes.func, - allowExtraBar: PropTypes.bool, - isDarkMode: PropTypes.bool, -}; - -/* eslint-enable react/no-set-state */ diff --git a/src/browser/components/NewTeamModal.jsx b/src/browser/components/NewTeamModal.jsx deleted file mode 100644 index 47fe3798..00000000 --- a/src/browser/components/NewTeamModal.jsx +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import PropTypes from 'prop-types'; -import {Modal, Button, FormGroup, FormControl, ControlLabel, HelpBlock} from 'react-bootstrap'; - -import urlUtils from '../../utils/url'; - -export default class NewTeamModal extends React.Component { - static defaultProps = { - restoreFocus: true, - }; - - constructor(props) { - super(props); - - this.wasShown = false; - this.state = { - teamName: '', - teamUrl: '', - teamOrder: props.currentOrder || 0, - saveStarted: false, - }; - } - - initializeOnShow() { - this.setState({ - teamName: this.props.team ? this.props.team.name : '', - teamUrl: this.props.team ? this.props.team.url : '', - teamIndex: this.props.team ? this.props.team.index : false, - teamOrder: this.props.team ? this.props.team.order : (this.props.currentOrder || 0), - saveStarted: false, - }); - } - - getTeamNameValidationError() { - if (!this.state.saveStarted) { - return null; - } - return this.state.teamName.length > 0 ? null : 'Name is required.'; - } - - getTeamNameValidationState() { - return this.getTeamNameValidationError() === null ? null : 'error'; - } - - handleTeamNameChange = (e) => { - this.setState({ - teamName: e.target.value, - }); - } - - getTeamUrlValidationError() { - if (!this.state.saveStarted) { - return null; - } - if (this.state.teamUrl.length === 0) { - return 'URL is required.'; - } - if (!(/^https?:\/\/.*/).test(this.state.teamUrl.trim())) { - return 'URL should start with http:// or https://.'; - } - if (!urlUtils.isValidURL(this.state.teamUrl.trim())) { - return 'URL is not formatted correctly.'; - } - return null; - } - - getTeamUrlValidationState() { - return this.getTeamUrlValidationError() === null ? null : 'error'; - } - - handleTeamUrlChange = (e) => { - this.setState({ - teamUrl: e.target.value, - }); - } - - getError() { - const nameError = this.getTeamNameValidationError(); - const urlError = this.getTeamUrlValidationError(); - - if (nameError && urlError) { - return 'Name and URL are required.'; - } else if (nameError) { - return nameError; - } else if (urlError) { - return urlError; - } - return null; - } - - validateForm() { - return this.getTeamNameValidationState() === null && - this.getTeamUrlValidationState() === null; - } - - save = () => { - this.setState({ - saveStarted: true, - }, () => { - if (this.validateForm()) { - this.props.onSave({ - url: this.state.teamUrl, - name: this.state.teamName, - index: this.state.teamIndex, - order: this.state.teamOrder, - }); - } - }); - } - - getSaveButtonLabel() { - if (this.props.editMode) { - return 'Save'; - } - return 'Add'; - } - - getModalTitle() { - if (this.props.editMode) { - return 'Edit Server'; - } - return 'Add Server'; - } - - render() { - if (this.wasShown !== this.props.show && this.props.show) { - this.initializeOnShow(); - } - this.wasShown = this.props.show; - - return ( - this.teamNameInputRef.focus()} - onHide={this.props.onClose} - restoreFocus={this.props.restoreFocus} - onKeyDown={(e) => { - switch (e.key) { - case 'Enter': - this.save(); - - // The add button from behind this might still be focused - e.preventDefault(); - e.stopPropagation(); - break; - case 'Escape': - this.props.onClose(); - break; - } - }} - > - - {this.getModalTitle()} - - - -
- - {'Server Display Name'} - { - this.teamNameInputRef = ref; - if (this.props.setInputRef) { - this.props.setInputRef(ref); - } - }} - onClick={(e) => { - e.stopPropagation(); - }} - autoFocus={true} - /> - - {'The name of the server displayed on your desktop app tab bar.'} - - - {'Server URL'} - { - e.stopPropagation(); - }} - /> - - {'The URL of your Mattermost server. Must start with http:// or https://.'} - -
-
- - -
- {this.getError()} -
- - - -
- -
- ); - } -} - -NewTeamModal.propTypes = { - onClose: PropTypes.func, - onSave: PropTypes.func, - team: PropTypes.object, - editMode: PropTypes.bool, - show: PropTypes.bool, - restoreFocus: PropTypes.bool, - currentOrder: PropTypes.number, - setInputRef: PropTypes.func, -}; diff --git a/src/browser/components/PermissionModal.jsx b/src/browser/components/PermissionModal.jsx deleted file mode 100644 index f81141aa..00000000 --- a/src/browser/components/PermissionModal.jsx +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -/* eslint-disable react/no-set-state */ - -import React from 'react'; -import {Modal, Button} from 'react-bootstrap'; -import {ipcRenderer, remote} from 'electron'; -import {log} from 'electron-log'; - -import {BASIC_AUTH_PERMISSION, REQUEST_PERMISSION_CHANNEL, DENY_PERMISSION_CHANNEL, GRANT_PERMISSION_CHANNEL, PERMISSION_DESCRIPTION} from '../../common/permissions'; - -import Util from '../../utils/util'; - -import ExternalLink from './externalLink.jsx'; - -function getKey(request, permission) { - return `${request.url}:${permission}`; -} - -export default class PermissionModal extends React.Component { - constructor(props) { - super(props); - this.state = { - tracker: new Map(), // permission request order is not preserved, but we won't have repetition of requests. - current: null, - }; - - ipcRenderer.on(REQUEST_PERMISSION_CHANNEL, (event, request, authInfo, permission) => { - switch (permission) { - case BASIC_AUTH_PERMISSION: - this.requestBasicAuthPermission(event, request, authInfo, permission); - break; - default: - console.warn(`Unknown permission request: ${permission}`); - ipcRenderer.send(DENY_PERMISSION_CHANNEL, request, permission); - } - }); - } - - requestBasicAuthPermission(event, request, authInfo, permission) { - const key = getKey(request, permission); - this.requestPermission(key, request.url, permission).then(() => { - ipcRenderer.send(GRANT_PERMISSION_CHANNEL, request.url, permission); - ipcRenderer.sendTo(remote.getCurrentWindow().webContents.id, 'login-request', request, authInfo); - this.loadNext(); - }).catch((err) => { - ipcRenderer.send(DENY_PERMISSION_CHANNEL, request.url, permission, err.message); - ipcRenderer.send('login-cancel', request); - this.loadNext(); - }); - } - - requestPermission(key, url, permission) { - return new Promise((resolve, reject) => { - const tracker = new Map(this.state.tracker); - const permissionRequest = { - grant: resolve, - deny: () => reject(new Error(`User denied ${permission} to ${url}`)), - url, - permission, - }; - tracker.set(key, permissionRequest); - const current = this.state.current ? this.state.current : key; - this.setState({ - tracker, - current, - }); - }); - } - - getCurrentData() { - if (this.state.current) { - return this.state.tracker.get(this.state.current); - } - return { - grant: () => { - const err = new Error(); - log.error(`There isn't any permission to grant access to.\n Stack trace:\n${err.stack}`); - }, - deny: () => { - const err = new Error(); - log.error(`There isn't any permission to deny access to.\n Stack trace:\n${err.stack}`); - } - }; - } - - loadNext() { - const tracker = new Map(this.state.tracker); - tracker.delete(this.state.current); - const nextKey = tracker.keys().next(); - const current = nextKey.done ? null : nextKey.value; - this.setState({ - tracker, - current, - }); - } - - getModalTitle() { - const {permission} = this.getCurrentData(); - return `${PERMISSION_DESCRIPTION[permission]} Required`; - } - - getModalBody() { - const {url, permission} = this.getCurrentData(); - const originDisplay = url ? Util.getHost(url) : 'unknown origin'; - const originLink = url ? originDisplay : ''; - return ( -
-

- {`A site that's not included in your Mattermost server configuration requires access for ${PERMISSION_DESCRIPTION[permission]}.`} -

-

- {'This request originated from '} - {`${originDisplay}`} -

-
- ); - } - - render() { - const {grant, deny} = this.getCurrentData(); - return ( - - - {this.getModalTitle()} - - - {this.getModalBody()} - - -
- - -
-
-
- ); - } -} -/* eslint-enable react/no-set-state */ diff --git a/src/browser/components/RemoveServerModal.jsx b/src/browser/components/RemoveServerModal.jsx deleted file mode 100644 index 95e7b994..00000000 --- a/src/browser/components/RemoveServerModal.jsx +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; -import {Modal} from 'react-bootstrap'; - -import DestructiveConfirmationModal from './DestructiveConfirmModal.jsx'; - -export default function RemoveServerModal(props) { - const {serverName, ...rest} = props; - return ( - -

- {'This will remove the server from your Desktop App but will not delete any of its data' + - ' - you can add the server back to the app at any time.'} -

-

- {'Confirm you wish to remove the '}{serverName}{' server?'} -

- - )} - /> - ); -} - -RemoveServerModal.propTypes = { - serverName: PropTypes.string.isRequired, -}; diff --git a/src/browser/components/SelectCertificateModal.jsx b/src/browser/components/SelectCertificateModal.jsx deleted file mode 100644 index dc867c27..00000000 --- a/src/browser/components/SelectCertificateModal.jsx +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {Fragment} from 'react'; -import PropTypes from 'prop-types'; -import {Modal, Button, Table, Row, Col} from 'react-bootstrap'; - -import ShowCertificateModal from './showCertificateModal.jsx'; - -export default class SelectCertificateModal extends React.Component { - static propTypes = { - onSelect: PropTypes.func.isRequired, - onCancel: PropTypes.func, - certificateRequests: PropTypes.arrayOf(PropTypes.shape({ - server: PropTypes.string, - certificateList: PropTypes.array, - })), - } - - constructor(props) { - super(props); - this.state = { - selectedIndex: null, - showCertificate: null, - }; - } - - selectfn = (index) => { - return (() => { - this.setState({selectedIndex: index}); - }); - }; - - renderCert = (cert, index) => { - const issuer = (cert.issuerName || (cert.issuer && cert.issuer.commonName) || ''); - const subject = (cert.subjectName || (cert.subject && cert.subject.commonName) || ''); - const serial = cert.serialNumber || ''; - - return ( - - {subject} - {issuer} - {serial} - ); - }; - - renderCerts = (certificateList) => { - if (certificateList) { - const certs = certificateList.map(this.renderCert); - return ( - - {certs} - - ); - } - return ({'No certificates available'}); - } - - getSelectedCert = () => { - return this.state.selectedIndex === null ? null : this.props.certificateRequests[0].certificateList[this.state.selectedIndex]; - }; - - handleOk = () => { - const cert = this.getSelectedCert(); - if (cert !== null) { - this.props.onSelect(cert); - } - } - - handleCertificateInfo = () => { - const certificate = this.getSelectedCert(); - this.setState({showCertificate: certificate}); - } - - certificateInfoClose = () => { - this.setState({showCertificate: null}); - } - - render() { - const certList = this.props.certificateRequests.length ? this.props.certificateRequests[0].certificateList : []; - const server = this.props.certificateRequests.length ? this.props.certificateRequests[0].server : ''; - if (this.state.showCertificate) { - return ( - - ); - } - return ( - 0} - > - - {'Select a certificate'} - - -

{`Select a certificate to authenticate yourself to ${server}`}

- - - - - - - - - - {this.renderCerts(certList)} - - -
{'Subject'}{'Issuer'}{'Serial'}
-
- -
- - - - - - - - - -
-
-
- ); - } -} diff --git a/src/browser/components/SettingsPage.jsx b/src/browser/components/SettingsPage.jsx deleted file mode 100644 index 668caa56..00000000 --- a/src/browser/components/SettingsPage.jsx +++ /dev/null @@ -1,1021 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// This file uses setState(). -/* eslint-disable react/no-set-state */ - -import os from 'os'; - -import React from 'react'; -import PropTypes from 'prop-types'; -import {Button, Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row} from 'react-bootstrap'; - -import {ipcRenderer, remote} from 'electron'; -import {debounce} from 'underscore'; -import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'; - -import Config from '../../common/config'; - -import restoreButton from '../../assets/titlebar/chrome-restore.svg'; -import maximizeButton from '../../assets/titlebar/chrome-maximize.svg'; -import minimizeButton from '../../assets/titlebar/chrome-minimize.svg'; -import closeButton from '../../assets/titlebar/chrome-close.svg'; - -import TeamList from './TeamList.jsx'; -import AutoSaveIndicator from './AutoSaveIndicator.jsx'; -import TabBar from './TabBar.jsx'; - -const CONFIG_TYPE_SERVERS = 'servers'; -const CONFIG_TYPE_APP_OPTIONS = 'appOptions'; - -const config = new Config(remote.app.getPath('userData') + '/config.json', remote.getCurrentWindow().registryConfigData); - -function backToIndex(index) { - const target = typeof index === 'undefined' ? 0 : index; - const indexURL = remote.getGlobal('isDev') ? 'http://localhost:8080/browser/index.html' : `file://${remote.app.getAppPath()}/browser/index.html`; - remote.getCurrentWindow().loadURL(`${indexURL}?index=${target}`); -} - -export default class SettingsPage extends React.Component { - constructor(props) { - super(props); - - this.state = this.convertConfigDataToState(config.data); - this.setState({ - maximized: false, - userOpenedDownloadGialog: false, - }); - - this.trayIconThemeRef = React.createRef(); - this.downloadLocationRef = React.createRef(); - - this.saveQueue = []; - } - - getTabWebContents() { - return remote.webContents.getFocusedWebContents(); - } - - componentDidMount() { - const self = this; - config.on('update', (configData) => { - this.updateSaveState(); - this.setState(this.convertConfigDataToState(configData, this.state)); - }); - - config.on('error', (error) => { - console.log('Config saving error: ', error); - - const savingState = Object.assign({}, this.state.savingState); - Object.entries(savingState).forEach(([configType, currentState]) => { - if (currentState !== AutoSaveIndicator.SAVING_STATE_DONE) { - savingState[configType] = AutoSaveIndicator.SAVING_STATE_ERROR; - this.setState({savingState}); - } - }); - }); - - function focusListener() { - self.setState({unfocused: false}); - } - - function blurListener() { - self.setState({unfocused: true}); - } - - const currentWindow = remote.getCurrentWindow(); - currentWindow.on('focus', focusListener); - currentWindow.on('blur', blurListener); - if (currentWindow.isMaximized()) { - self.setState({maximized: true}); - } - currentWindow.on('maximize', this.handleMaximizeState); - currentWindow.on('unmaximize', this.handleMaximizeState); - - if (currentWindow.isFullScreen()) { - self.setState({fullScreen: true}); - } - currentWindow.on('enter-full-screen', this.handleFullScreenState); - currentWindow.on('leave-full-screen', this.handleFullScreenState); - - // when the config object changes here in the renderer process, tell the main process to reload its config object to get the changes - config.on('synchronize', () => { - ipcRenderer.send('reload-config'); - }); - - // listen for any config reload requests from the main process to reload configuration changes here in the renderer process - ipcRenderer.on('reload-config', () => { - config.reload(); - }); - - ipcRenderer.on('add-server', () => { - this.setState({ - showAddTeamForm: true, - }); - }); - - ipcRenderer.on('switch-tab', (event, key) => { - backToIndex(key); - }); - - ipcRenderer.on('zoom-in', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - if (activeTabWebContents.zoomLevel >= 9) { - return; - } - activeTabWebContents.zoomLevel += 1; - }); - - ipcRenderer.on('zoom-out', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - if (activeTabWebContents.zoomLevel <= -8) { - return; - } - activeTabWebContents.zoomLevel -= 1; - }); - - ipcRenderer.on('zoom-reset', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.zoomLevel = 0; - }); - - ipcRenderer.on('undo', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.undo(); - }); - - ipcRenderer.on('redo', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.redo(); - }); - - ipcRenderer.on('cut', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.cut(); - }); - - ipcRenderer.on('copy', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.copy(); - }); - - ipcRenderer.on('paste', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.paste(); - }); - - ipcRenderer.on('paste-and-match', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.pasteAndMatchStyle(); - }); - - if (process.platform === 'darwin') { - self.setState({ - isDarkMode: remote.nativeTheme.shouldUseDarkColors, - }); - remote.systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => { - self.setState({ - isDarkMode: remote.nativeTheme.shouldUseDarkColors, - }); - }); - } else { - self.setState({ - isDarkMode: this.props.getDarkMode(), - }); - - ipcRenderer.on('set-dark-mode', () => { - this.setDarkMode(); - }); - - this.threeDotMenu = React.createRef(); - ipcRenderer.on('focus-three-dot-menu', () => { - if (this.threeDotMenu.current) { - this.threeDotMenu.current.focus(); - } - }); - } - } - - convertConfigDataToState = (configData, currentState = {}) => { - const newState = Object.assign({}, configData); - newState.showAddTeamForm = currentState.showAddTeamForm || false; - newState.trayWasVisible = currentState.trayWasVisible || remote.getCurrentWindow().trayWasVisible; - if (newState.teams.length === 0 && currentState.firstRun !== false) { - newState.firstRun = false; - newState.showAddTeamForm = true; - } - newState.savingState = currentState.savingState || { - appOptions: AutoSaveIndicator.SAVING_STATE_DONE, - servers: AutoSaveIndicator.SAVING_STATE_DONE, - }; - return newState; - } - - saveSetting = (configType, {key, data}) => { - this.saveQueue.push({ - configType, - key, - data, - }); - this.updateSaveState(); - this.processSaveQueue(); - } - - processSaveQueue = debounce(() => { - config.setMultiple(this.saveQueue.splice(0, this.saveQueue.length)); - }, 500); - - updateSaveState = () => { - let queuedUpdateCounts = { - [CONFIG_TYPE_SERVERS]: 0, - [CONFIG_TYPE_APP_OPTIONS]: 0, - }; - - queuedUpdateCounts = this.saveQueue.reduce((updateCounts, {configType}) => { - updateCounts[configType]++; - return updateCounts; - }, queuedUpdateCounts); - - const savingState = Object.assign({}, this.state.savingState); - - Object.entries(queuedUpdateCounts).forEach(([configType, count]) => { - if (count > 0) { - savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVING; - } else if (count === 0 && savingState[configType] === AutoSaveIndicator.SAVING_STATE_SAVING) { - savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVED; - this.resetSaveState(configType); - } - }); - - this.setState({savingState}); - } - - resetSaveState = debounce((configType) => { - if (this.state.savingState[configType] !== AutoSaveIndicator.SAVING_STATE_SAVING) { - const savingState = Object.assign({}, this.state.savingState); - savingState[configType] = AutoSaveIndicator.SAVING_STATE_DONE; - this.setState({savingState}); - } - }, 2000); - - handleTeamsChange = (teams) => { - setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); - this.setState({ - showAddTeamForm: false, - teams, - }); - if (teams.length === 0) { - this.setState({showAddTeamForm: true}); - } - } - - handleCancel = () => { - backToIndex(); - } - - handleChangeShowTrayIcon = () => { - const shouldShowTrayIcon = !this.refs.showTrayIcon.props.checked; - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon}); - this.setState({ - showTrayIcon: shouldShowTrayIcon, - }); - - if (process.platform === 'darwin' && !shouldShowTrayIcon) { - this.setState({ - minimizeToTray: false, - }); - } - } - - handleChangeTrayIconTheme = (theme) => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'trayIconTheme', data: theme}); - this.setState({ - trayIconTheme: theme, - }); - } - - handleChangeAutoStart = () => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: !this.refs.autostart.props.checked}); - this.setState({ - autostart: !this.refs.autostart.props.checked, - }); - } - - handleChangeMinimizeToTray = () => { - const shouldMinimizeToTray = this.state.showTrayIcon && !this.refs.minimizeToTray.props.checked; - - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray}); - this.setState({ - minimizeToTray: shouldMinimizeToTray, - }); - } - - toggleShowTeamForm = () => { - this.setState({ - showAddTeamForm: !this.state.showAddTeamForm, - }); - document.activeElement.blur(); - } - - setShowTeamFormVisibility = (val) => { - this.setState({ - showAddTeamForm: val, - }); - } - - handleFlashWindow = () => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { - key: 'notifications', - data: { - ...this.state.notifications, - flashWindow: this.refs.flashWindow.props.checked ? 0 : 2, - }, - }); - this.setState({ - notifications: { - ...this.state.notifications, - flashWindow: this.refs.flashWindow.props.checked ? 0 : 2, - }, - }); - } - - handleBounceIcon = () => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { - key: 'notifications', - data: { - ...this.state.notifications, - bounceIcon: !this.refs.bounceIcon.props.checked, - }, - }); - this.setState({ - notifications: { - ...this.state.notifications, - bounceIcon: !this.refs.bounceIcon.props.checked, - }, - }); - } - - handleBounceIconType = (event) => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { - key: 'notifications', - data: { - ...this.state.notifications, - bounceIconType: event.target.value, - }, - }); - this.setState({ - notifications: { - ...this.state.notifications, - bounceIconType: event.target.value, - }, - }); - } - - handleShowUnreadBadge = () => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: !this.refs.showUnreadBadge.props.checked}); - this.setState({ - showUnreadBadge: !this.refs.showUnreadBadge.props.checked, - }); - } - - handleChangeUseSpellChecker = () => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: !this.refs.useSpellChecker.props.checked}); - this.setState({ - useSpellChecker: !this.refs.useSpellChecker.props.checked, - }); - } - - handleChangeEnableHardwareAcceleration = () => { - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: !this.refs.enableHardwareAcceleration.props.checked}); - this.setState({ - enableHardwareAcceleration: !this.refs.enableHardwareAcceleration.props.checked, - }); - } - - saveDownloadLocation = (location) => { - this.setState({ - downloadLocation: location, - }); - setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'downloadLocation', data: location}); - } - - handleChangeDownloadLocation = (e) => { - this.saveDownloadLocation(e.target.value); - } - - selectDownloadLocation = () => { - if (!this.state.userOpenedDownloadGialog) { - const message = 'Specify the folder where files will download'; - remote.dialog.showOpenDialog({defaultPath: `/Users/${process.env.USER || process.env.USERNAME}/Downloads`, - message, - properties: - ['openDirectory', 'createDirectory', 'dontAddToRecent', 'promptToCreate']}).then((result) => this.saveDownloadLocation(result.filePaths[0])); - this.setState({userOpenedDownloadGialog: true}); - } - this.setState({userOpenedDownloadGialog: false}); - } - - updateTeam = (index, newData) => { - const teams = this.state.localTeams; - teams[index] = newData; - setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); - this.setState({ - teams, - }); - } - - addServer = (team) => { - const teams = this.state.localTeams; - teams.push(team); - setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); - this.setState({ - teams, - }); - } - - setDarkMode() { - this.setState({ - isDarkMode: this.props.setDarkMode(), - }); - } - - handleClose = () => { - const win = remote.getCurrentWindow(); - win.close(); - } - - handleMinimize = () => { - const win = remote.getCurrentWindow(); - win.minimize(); - } - - handleMaximize = () => { - const win = remote.getCurrentWindow(); - win.maximize(); - } - - handleRestore = () => { - const win = remote.getCurrentWindow(); - win.restore(); - } - - openMenu = () => { - // @eslint-ignore - this.threeDotMenu.current.blur(); - this.props.openMenu(); - } - - handleDoubleClick = () => { - if (process.platform === 'darwin') { - const doubleClickAction = remote.systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string'); - const win = remote.getCurrentWindow(); - if (doubleClickAction === 'Minimize') { - win.minimize(); - } else if (!win.isMaximized()) { - win.maximize(); - } else if (win.isMaximized()) { - win.unmaximize(); - } - } - } - - handleMaximizeState = () => { - const win = remote.getCurrentWindow(); - this.setState({maximized: win.isMaximized()}); - } - - handleFullScreenState = () => { - const win = remote.getCurrentWindow(); - this.setState({fullScreen: win.isFullScreen()}); - } - - render() { - const tabsRow = ( - - ); - - let topBarClassName = 'topBar'; - if (process.platform === 'darwin') { - topBarClassName += ' macOS'; - } - if (this.state.isDarkMode) { - topBarClassName += ' darkMode'; - } - if (this.state.fullScreen) { - topBarClassName += ' fullScreen'; - } - - let maxButton; - if (this.state.maximized) { - maxButton = ( -
- -
- ); - } else { - maxButton = ( -
- -
- ); - } - - let overlayGradient; - if (process.platform !== 'darwin') { - overlayGradient = ( - - ); - } - - let titleBarButtons; - if (os.platform() === 'win32' && os.release().startsWith('10')) { - titleBarButtons = ( - -
- -
- {maxButton} -
- -
-
- ); - } - - const topRow = ( - -
- - {tabsRow} - {overlayGradient} - {titleBarButtons} -
-
- ); - - const settingsPage = { - navbar: { - backgroundColor: '#fff', - position: 'relative', - }, - close: { - textDecoration: 'none', - position: 'absolute', - right: '0', - top: '5px', - fontSize: '35px', - fontWeight: 'normal', - color: '#bbb', - }, - heading: { - textAlign: 'center', - fontSize: '24px', - margin: '0', - padding: '1em 0', - }, - sectionHeading: { - fontSize: '20px', - margin: '0', - padding: '1em 0', - float: 'left', - }, - sectionHeadingLink: { - marginTop: '24px', - display: 'inline-block', - fontSize: '15px', - }, - footer: { - padding: '0.4em 0', - }, - - downloadLocationInput: { - marginRight: '3px', - marginTop: '8px', - width: '320px', - height: '34px', - padding: '0 12px', - borderRadius: '4px', - border: '1px solid #ccc', - fontWeight: '500', - }, - - downloadLocationButton: { - marginBottom: '4px', - }, - - container: { - paddingBottom: '40px', - } - }; - - const teamsRow = ( - - - { - backToIndex(this.state.localTeams[index].order + this.state.buildTeams.length + this.state.registryTeams.length); - }} - /> - - - ); - - const serversRow = ( - - -

{'Server Management'}

-
- -
- - -

- {'+ Add New Server'} -

- -
- ); - - let srvMgmt; - if (this.state.enableServerManagement === true) { - srvMgmt = ( -
- {serversRow} - {teamsRow} -
-
- ); - } - - const options = []; - - // MacOS has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX - if (process.platform === 'win32' || process.platform === 'linux') { - options.push( - - {'Start app on login'} - - {'If enabled, the app starts automatically when you log in to your machine.'} - - ); - } - - options.push( - - {'Check spelling'} - - {'Highlight misspelled words in your messages.'} - {' Available for English, French, German, Portuguese, Russian, Ukrainian, Spanish, and Dutch.'} - - ); - - if (process.platform === 'darwin' || process.platform === 'win32') { - const TASKBAR = process.platform === 'win32' ? 'taskbar' : 'Dock'; - options.push( - - {`Show red badge on ${TASKBAR} icon to indicate unread messages`} - - {`Regardless of this setting, mentions are always indicated with a red badge and item count on the ${TASKBAR} icon.`} - - ); - } - - if (process.platform === 'win32' || process.platform === 'linux') { - options.push( - - {'Flash app window and taskbar icon when a new message is received'} - - {'If enabled, app window and taskbar icon flash for a few seconds when a new message is received.'} - - ); - } - - if (process.platform === 'darwin') { - options.push( - - - {'Bounce the Dock icon'} - - - {'once'} - - {' '} - - {'until I open the app'} - - - {'If enabled, the Dock icon bounces once or until the user opens the app when a new notification is received.'} - - - ); - } - - if (process.platform === 'darwin' || process.platform === 'linux') { - options.push( - - {process.platform === 'darwin' ? `Show ${remote.app.name} icon in the menu bar` : 'Show icon in the notification area'} - - {'Setting takes effect after restarting the app.'} - - ); - } - - if (process.platform === 'linux') { - options.push( - - {'Icon theme: '} - this.handleChangeTrayIconTheme('light', event)} - > - {'Light'} - - {' '} - this.handleChangeTrayIconTheme('dark', event)} - >{'Dark'} - - ); - } - - if (process.platform === 'linux') { - options.push( - - {'Leave app running in notification area when application window is closed'} - - {'If enabled, the app stays running in the notification area after app window is closed.'} - {this.state.trayWasVisible || !this.state.showTrayIcon ? '' : ' Setting takes effect after restarting the app.'} - - ); - } - - options.push( - - {'Use GPU hardware acceleration'} - - {'If enabled, Mattermost UI is rendered more efficiently but can lead to decreased stability for some systems.'} - {' Setting takes effect after restarting the app.'} - - - ); - - options.push( -
-
-
{'Download Location'}
- - - - {'Specify the folder where files will download.'} - -
- ); - - const optionsRow = (options.length > 0) ? ( - - -

{'App Options'}

-
- -
- { options.map((opt, i) => ( - - {opt} - - )) } - -
- ) : null; - - return ( -
- { topRow } -
- -
-

{'Settings'}

- -
-
- - { srvMgmt } - { optionsRow } - -
-
- ); - } -} - -SettingsPage.propTypes = { - getDarkMode: PropTypes.func.isRequired, - setDarkMode: PropTypes.func.isRequired, - openMenu: PropTypes.func.isRequired, -}; - -/* eslint-enable react/no-set-state */ diff --git a/src/browser/components/TabBar.jsx b/src/browser/components/TabBar.jsx deleted file mode 100644 index c1816fa0..00000000 --- a/src/browser/components/TabBar.jsx +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import {remote} from 'electron'; -import PropTypes from 'prop-types'; -import {Nav, NavItem} from 'react-bootstrap'; -import {Container, Draggable} from 'react-smooth-dnd'; -import PlusIcon from 'mdi-react/PlusIcon'; - -export default class TabBar extends React.Component { // need "this" - render() { - const orderedTabs = this.props.teams.concat().sort((a, b) => a.order - b.order); - const tabs = orderedTabs.map((team) => { - const index = this.props.teams.indexOf(team); - const sessionExpired = this.props.sessionsExpired[index]; - - let unreadCount = 0; - if (this.props.unreadCounts[index] > 0) { - unreadCount = this.props.unreadCounts[index]; - } - if (this.props.unreadAtActive[index]) { - unreadCount += 1; - } - - let mentionCount = 0; - if (this.props.mentionCounts[index] > 0) { - mentionCount = this.props.mentionCounts[index]; - } - if (this.props.mentionAtActiveCounts[index] > 0) { - mentionCount += this.props.mentionAtActiveCounts[index]; - } - - let badgeDiv; - if (sessionExpired) { - badgeDiv = ( -
- ); - } else if (mentionCount !== 0) { - badgeDiv = ( -
- {mentionCount} -
- ); - } else if (unreadCount !== 0) { - badgeDiv = ( -
- ); - } - - const id = `teamTabItem${index}`; - const navItem = () => ( - { - this.props.onSelect(index); - }} - onSelect={() => { - this.props.onSelect(index); - }} - title={team.name} - > -
- - {team.name} - - { badgeDiv } -
-
- ); - - return ( - ); - }); - if (this.props.showAddServerButton === true) { - tabs.push( - { - this.props.onAddServer(); - }} - > -
- -
-
- ); - } - - const navContainer = (ref) => ( - - ); - return ( - { - return !(remote.getCurrentWindow().registryConfigData.teams && remote.getCurrentWindow().registryConfigData.teams.length > 0); - }} - /> - ); - } -} - -TabBar.propTypes = { - activeKey: PropTypes.number, - id: PropTypes.string, - isDarkMode: PropTypes.bool, - onSelect: PropTypes.func, - teams: PropTypes.array, - sessionsExpired: PropTypes.array, - unreadCounts: PropTypes.array, - unreadAtActive: PropTypes.array, - mentionCounts: PropTypes.array, - mentionAtActiveCounts: PropTypes.array, - showAddServerButton: PropTypes.bool, - onAddServer: PropTypes.func, - onDrop: PropTypes.func, -}; diff --git a/src/browser/components/TeamList.jsx b/src/browser/components/TeamList.jsx deleted file mode 100644 index 8b86fe64..00000000 --- a/src/browser/components/TeamList.jsx +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; -import {ListGroup} from 'react-bootstrap'; - -import TeamListItem from './TeamListItem.jsx'; -import NewTeamModal from './NewTeamModal.jsx'; -import RemoveServerModal from './RemoveServerModal.jsx'; - -export default class TeamList extends React.Component { - constructor(props) { - super(props); - - this.state = { - showEditTeamForm: false, - indexToRemoveServer: -1, - team: { - url: '', - name: '', - index: false, - order: props.teams.length, - }, - }; - } - - handleTeamRemove = (index) => { - console.log(index); - const teams = this.props.teams; - const removedOrder = this.props.teams[index].order; - teams.splice(index, 1); - teams.forEach((value) => { - if (value.order > removedOrder) { - value.order--; - } - }); - this.props.onTeamsChange(teams); - } - - handleTeamAdd = (team) => { - const teams = this.props.teams; - - // check if team already exists and then change existing team or add new one - if ((typeof team.index !== 'undefined') && teams[team.index]) { - teams[team.index].name = team.name; - teams[team.index].url = team.url; - teams[team.index].order = team.order; - } else { - teams.push(team); - } - - this.setState({ - showEditTeamForm: false, - team: { - url: '', - name: '', - index: false, - order: teams.length, - }, - }); - - this.props.onTeamsChange(teams); - } - - handleTeamEditing = (teamName, teamUrl, teamIndex, teamOrder) => { - this.setState({ - showEditTeamForm: true, - team: { - url: teamUrl, - name: teamName, - index: teamIndex, - order: teamOrder, - }, - }); - } - - openServerRemoveModal = (indexForServer) => { - this.setState({indexToRemoveServer: indexForServer}); - } - - closeServerRemoveModal = () => { - this.setState({indexToRemoveServer: -1}); - } - - render() { - const self = this; - const teamNodes = this.props.teams.map((team, i) => { - function handleTeamRemove() { - document.activeElement.blur(); - self.openServerRemoveModal(i); - } - - function handleTeamEditing() { - document.activeElement.blur(); - self.handleTeamEditing(team.name, team.url, i, team.order); - } - - function handleTeamClick() { - self.props.onTeamClick(i); - } - - return ( - - ); - }); - - const addServerForm = ( - { - this.setState({ - showEditTeamForm: false, - team: { - name: '', - url: '', - index: false, - order: this.props.teams.length, - }, - }); - this.props.setAddTeamFormVisibility(false); - }} - onSave={(newTeam) => { - const teamData = { - name: newTeam.name, - url: newTeam.url, - order: newTeam.order, - }; - if (this.props.showAddTeamForm) { - this.props.addServer(teamData); - } else { - this.props.updateTeam(newTeam.index, teamData); - } - this.setState({ - showNewTeamModal: false, - showEditTeamForm: false, - team: { - name: '', - url: '', - index: false, - order: newTeam.order + 1, - }, - }); - this.render(); - this.props.setAddTeamFormVisibility(false); - }} - team={this.state.team} - />); - - const removeServer = this.props.teams[this.state.indexToRemoveServer]; - const removeServerModal = ( - { - this.handleTeamRemove(this.state.indexToRemoveServer); - this.closeServerRemoveModal(); - }} - /> - ); - - return ( - - { teamNodes } - { addServerForm } - { removeServerModal} - - ); - } -} - -TeamList.propTypes = { - onTeamsChange: PropTypes.func, - showAddTeamForm: PropTypes.bool, - teams: PropTypes.array, - addServer: PropTypes.func, - updateTeam: PropTypes.func, - toggleAddTeamForm: PropTypes.func, - setAddTeamFormVisibility: PropTypes.func, - onTeamClick: PropTypes.func, -}; diff --git a/src/browser/components/TeamListItem.jsx b/src/browser/components/TeamListItem.jsx deleted file mode 100644 index facb6f04..00000000 --- a/src/browser/components/TeamListItem.jsx +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class TeamListItem extends React.Component { - handleTeamRemove = () => { - this.props.onTeamRemove(); - } - handleTeamEditing = () => { - this.props.onTeamEditing(); - } - render() { - return ( -
-
-

{ this.props.name }

-

- { this.props.url } -

-
-
- {'Edit'} - {' - '} - {'Remove'} -
-
- ); - } -} - -TeamListItem.propTypes = { - name: PropTypes.string, - onTeamEditing: PropTypes.func, - onTeamRemove: PropTypes.func, - onTeamClick: PropTypes.func, - url: PropTypes.string, -}; diff --git a/src/browser/components/UpdaterPage.jsx b/src/browser/components/UpdaterPage.jsx deleted file mode 100644 index 05d7c4df..00000000 --- a/src/browser/components/UpdaterPage.jsx +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import propTypes from 'prop-types'; -import {Button, Navbar, ProgressBar} from 'react-bootstrap'; - -function InstallButton(props) { - if (props.notifyOnly) { - return ( - - ); - } - return ( - - ); -} - -InstallButton.propTypes = { - notifyOnly: propTypes.bool.isRequired, - onClickInstall: propTypes.func.isRequired, - onClickDownload: propTypes.func.isRequired, -}; - -function UpdaterPage(props) { - return ( -
- -

{'New update is available'}

-
-
-

{`A new version of the ${props.appName} is available!`}

-

{'Read the '} - {'release notes'} - {' to learn more.'} -

-
- {props.isDownloading ? - - -
- -
-
: - - -
- - -
-
- } -
- ); -} - -UpdaterPage.propTypes = { - appName: propTypes.string.isRequired, - notifyOnly: propTypes.bool.isRequired, - isDownloading: propTypes.bool.isRequired, - progress: propTypes.number, - onClickInstall: propTypes.func.isRequired, - onClickDownload: propTypes.func.isRequired, - onClickReleaseNotes: propTypes.func.isRequired, - onClickRemind: propTypes.func.isRequired, - onClickSkip: propTypes.func.isRequired, - onClickCancel: propTypes.func.isRequired, -}; - -export default UpdaterPage; diff --git a/src/browser/components/UpdaterPage/UpdaterPage.stories.jsx b/src/browser/components/UpdaterPage/UpdaterPage.stories.jsx deleted file mode 100644 index 60212b57..00000000 --- a/src/browser/components/UpdaterPage/UpdaterPage.stories.jsx +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {storiesOf} from '@storybook/react'; - -import {action} from '@storybook/addon-actions'; - -import UpdaterPage from '../UpdaterPage.jsx'; -import '../../css/components/UpdaterPage.css'; - -/* -appName: propTypes.string.isRequired, -notifyOnly: propTypes.bool.isRequired, -isDownloading: propTypes.bool.isRequired, -progress: propTypes.number, -onClickInstall: propTypes.func.isRequired, -onClickDownload: propTypes.func.isRequired, -onClickReleaseNotes: propTypes.func.isRequired, -onClickRemind: propTypes.func.isRequired, -onClickSkip: propTypes.func.isRequired, -*/ -const appName = 'Storybook App'; - -storiesOf('UpdaterPage', module). - add('Normal', () => ( - - )). - add('NotifyOnly', () => ( - - )). - add('Downloading', () => ( - - )); diff --git a/src/browser/components/externalLink.jsx b/src/browser/components/externalLink.jsx deleted file mode 100644 index 82897ed0..00000000 --- a/src/browser/components/externalLink.jsx +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import PropTypes from 'prop-types'; -import {ipcRenderer} from 'electron'; - -import urlUtils from '../../utils/url'; - -// this component is used to override some checks from the UI, leaving only to trust the protocol in case it wasn't http/s -// it is used the same as an `a` JSX tag -export default function ExternalLink(props) { - const click = (e) => { - e.preventDefault(); - let parseUrl; - try { - parseUrl = urlUtils.parseURL(props.href); - ipcRenderer.send('confirm-protocol', parseUrl.protocol, props.href); - } catch (err) { - console.error(`invalid url ${props.href} supplied to externallink: ${err}`); - } - }; - const options = { - onClick: click, - ...props, - }; - return ( - - ); -} - -ExternalLink.propTypes = { - href: PropTypes.string.isRequired, -}; diff --git a/src/browser/components/showCertificateModal.jsx b/src/browser/components/showCertificateModal.jsx deleted file mode 100644 index ccdfec8e..00000000 --- a/src/browser/components/showCertificateModal.jsx +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {Fragment} from 'react'; -import PropTypes from 'prop-types'; -import {Modal, Button, Row, Col} from 'react-bootstrap'; - -export default class ShowCertificateModal extends React.Component { - static propTypes = { - certificate: PropTypes.object, - onOk: PropTypes.func.isRequired, - }; - - constructor(props) { - super(props); - this.state = { - certificate: props.certificate, - }; - } - - handleOk = () => { - this.setState({certificate: null}); - this.props.onOk(); - } - - render() { - const certificateSection = (descriptor) => { - return ( - -
{descriptor}
-
-
- ); - }; - const certificateItem = (descriptor, value) => { - const val = value ? `${value}` : ; - return ( - -
{descriptor}
-
{val}
-
- ); - }; - - if (this.state.certificate === null) { - return ( - - - {'No certificate Selected'} - - - ); - } - - const utcSeconds = (date) => { - const d = new Date(0); - d.setUTCSeconds(date); - return d; - }; - - const expiration = utcSeconds(this.state.certificate.validExpiry); - const creation = utcSeconds(this.state.certificate.validStart); - const dateDisplayOptions = {dateStyle: 'full', timeStyle: 'full'}; - const dateLocale = 'en-US'; - return ( - - - {'Certificate information'} - - -

{'Details'}

-
- {certificateSection('Subject Name')} - {certificateItem('Common Name', this.state.certificate.subject.commonName)} -
-
- {certificateSection('Issuer Name')} - {certificateItem('Common Name', this.state.certificate.issuer.commonName)} -
-
- {certificateItem('Serial Number', this.state.certificate.serialNumber)} - {certificateItem('Not Valid Before', creation.toLocaleString(dateLocale, dateDisplayOptions))} - {certificateItem('Not Valid After', expiration.toLocaleString(dateLocale, dateDisplayOptions))} -
-
- {certificateSection('Public Key Info')} - {certificateItem('Algorithm', this.state.certificate.fingerprint.split('/')[0])} -
-
- -
- - - - - -
-
-
- ); - } -} \ No newline at end of file diff --git a/src/browser/hooks/useTransitionEnd.js b/src/browser/hooks/useTransitionEnd.js deleted file mode 100644 index e98a566d..00000000 --- a/src/browser/hooks/useTransitionEnd.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; - -/** - * A custom hook to implement a transitionend listener on the provided ref - * @param {object} ref - A reference to a DOM element to add the listener to - * @param {function} callback - A callback function that will be run for matching animation events - * @param {array} properties - An array of css property strings to listen for - * @param {boolean} listenForEventBubbling - A parameter that when true, listens for events on the target element and - * bubbled from all descendent elements but when false, only listens for events coming from the target element and - * ignores events bubbling up from descendent elements - */ -function useTransitionend( - ref, - callback, - properties, - listenForEventBubbling = true -) { - React.useEffect(() => { - if (!ref.current) { - return; - } - - function handleTransitionEnd(event) { - if (!listenForEventBubbling && event.target !== ref.current) { - return; - } - - if (properties && typeof properties === 'object') { - const property = properties.find( - (propertyName) => propertyName === event.propertyName - ); - if (property) { - callback(event); - } - return; - } - callback(event); - } - - ref.current.addEventListener('transitionend', handleTransitionEnd); - - return () => { - if (!ref.current) { - return; - } - ref.current.removeEventListener( - 'transitionend', - handleTransitionEnd - ); - }; - }, [ref, callback, properties, listenForEventBubbling]); -} - -export default useTransitionend; diff --git a/src/browser/index.html b/src/browser/index.html deleted file mode 100644 index 5ef4de78..00000000 --- a/src/browser/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - -
- - - - diff --git a/src/browser/index.jsx b/src/browser/index.jsx deleted file mode 100644 index aa33dd16..00000000 --- a/src/browser/index.jsx +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import './css/index.css'; - -window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval - throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.'); -}; - -import React from 'react'; -import ReactDOM from 'react-dom'; -import {remote, ipcRenderer} from 'electron'; - -import urlUtils from '../utils/url'; - -import Config from '../common/config'; - -import EnhancedNotification from './js/notification'; -import MainPage from './components/MainPage.jsx'; -import {createDataURL as createBadgeDataURL} from './js/badge'; - -Notification = EnhancedNotification; // eslint-disable-line no-global-assign, no-native-reassign - -const config = new Config(remote.app.getPath('userData') + '/config.json', remote.getCurrentWindow().registryConfigData); - -const teams = config.teams; - -remote.getCurrentWindow().removeAllListeners('focus'); - -if (teams.length === 0) { - remote.getCurrentWindow().loadFile('browser/settings.html'); -} - -const parsedURLSearchParams = urlUtils.parseURL(window.location.href).searchParams; -const parsedURLHasIndex = parsedURLSearchParams.has('index'); -const initialIndex = parsedURLHasIndex ? parseInt(parsedURLSearchParams.get('index'), 10) : getInitialIndex(); - -let deeplinkingUrl = null; -if (!parsedURLHasIndex) { - deeplinkingUrl = remote.getCurrentWindow().deeplinkingUrl; -} - -config.on('update', (configData) => { - teams.splice(0, teams.length, ...configData.teams); -}); - -config.on('synchronize', () => { - ipcRenderer.send('reload-config'); -}); - -ipcRenderer.on('reload-config', () => { - config.reload(); -}); - -function getInitialIndex() { - const element = teams.find((e) => e.order === 0); - return element ? teams.indexOf(element) : 0; -} - -function showBadgeWindows(sessionExpired, unreadCount, mentionCount) { - function sendBadge(dataURL, description) { - // window.setOverlayIcon() does't work with NativeImage across remote boundaries. - // https://github.com/atom/electron/issues/4011 - ipcRenderer.send('update-unread', { - overlayDataURL: dataURL, - description, - sessionExpired, - unreadCount, - mentionCount, - }); - } - - if (sessionExpired) { - const dataURL = createBadgeDataURL('•'); - sendBadge(dataURL, 'Session Expired: Please sign in to continue receiving notifications.'); - } else if (mentionCount > 0) { - const dataURL = createBadgeDataURL((mentionCount > 99) ? '99+' : mentionCount.toString(), mentionCount > 99); - sendBadge(dataURL, 'You have unread mentions (' + mentionCount + ')'); - } else if (unreadCount > 0 && config.showUnreadBadge) { - const dataURL = createBadgeDataURL('•'); - sendBadge(dataURL, 'You have unread channels (' + unreadCount + ')'); - } else { - sendBadge(null, 'You have no unread messages'); - } -} - -function showBadgeOSX(sessionExpired, unreadCount, mentionCount) { - if (sessionExpired) { - remote.app.dock.setBadge('•'); - } else if (mentionCount > 0) { - remote.app.dock.setBadge(mentionCount.toString()); - } else if (unreadCount > 0 && config.showUnreadBadge) { - remote.app.dock.setBadge('•'); - } else { - remote.app.dock.setBadge(''); - } - - ipcRenderer.send('update-unread', { - sessionExpired, - unreadCount, - mentionCount, - }); -} - -function showBadgeLinux(sessionExpired, unreadCount, mentionCount) { - if (remote.app.isUnityRunning()) { - if (sessionExpired) { - remote.app.badgeCount = mentionCount + 1; - } else { - remote.app.badgeCount = mentionCount; - } - } - - ipcRenderer.send('update-unread', { - sessionExpired, - unreadCount, - mentionCount, - }); -} - -function showBadge(sessionExpired, unreadCount, mentionCount) { - switch (process.platform) { - case 'win32': - showBadgeWindows(sessionExpired, unreadCount, mentionCount); - break; - case 'darwin': - showBadgeOSX(sessionExpired, unreadCount, mentionCount); - break; - case 'linux': - showBadgeLinux(sessionExpired, unreadCount, mentionCount); - break; - } -} - -function teamConfigChange(updatedTeams, callback) { - config.set('teams', updatedTeams); - if (callback) { - config.once('update', callback); - } -} - -function handleSelectSpellCheckerLocale(locale) { - config.set('spellCheckerLocale', locale); - ipcRenderer.send('update-dict', locale); -} - -function moveTabs(originalOrder, newOrder) { - const tabOrder = teams.concat().map((team, index) => { - return { - index, - order: team.order, - }; - }).sort((a, b) => (a.order - b.order)); - - const team = tabOrder.splice(originalOrder, 1); - tabOrder.splice(newOrder, 0, team[0]); - - let teamIndex; - tabOrder.forEach((t, order) => { - if (order === newOrder) { - teamIndex = t.index; - } - teams[t.index].order = order; - }); - teamConfigChange(teams); - return teamIndex; -} - -function getDarkMode() { - if (process.platform !== 'darwin') { - return config.darkMode; - } - return null; -} - -function setDarkMode() { - if (process.platform !== 'darwin') { - const darkMode = Boolean(config.darkMode); - config.set('darkMode', !darkMode); - return !darkMode; - } - return null; -} - -function openMenu() { - if (process.platform !== 'darwin') { - ipcRenderer.send('open-app-menu'); - } -} - -ReactDOM.render( - , - document.getElementById('content') -); - -// Deny drag&drop navigation in mainWindow. -// Drag&drop is allowed in webview of index.html. -document.addEventListener('dragover', (event) => event.preventDefault()); -document.addEventListener('drop', (event) => event.preventDefault()); diff --git a/src/browser/js/badge.js b/src/browser/js/badge.js deleted file mode 100644 index 8120c085..00000000 --- a/src/browser/js/badge.js +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -'use strict'; - -export function createDataURL(text, small) { - const scale = 2; // should rely display dpi - const size = (small ? 20 : 16) * scale; - const canvas = document.createElement('canvas'); - canvas.setAttribute('width', size); - canvas.setAttribute('height', size); - const ctx = canvas.getContext('2d'); - - // circle - ctx.fillStyle = '#FF1744'; // Material Red A400 - ctx.beginPath(); - ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2); - ctx.fill(); - - // text - ctx.fillStyle = '#ffffff'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.font = (11 * scale) + 'px sans-serif'; - ctx.fillText(text, size / 2, size / 2, size); - - return canvas.toDataURL(); -} diff --git a/src/browser/js/contextMenu.js b/src/browser/js/contextMenu.js deleted file mode 100644 index 14199082..00000000 --- a/src/browser/js/contextMenu.js +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import {ipcRenderer, remote} from 'electron'; -import electronContextMenu from 'electron-context-menu'; - -import urlUtils from '../../utils/url'; - -function getSuggestionsMenus(webcontents, suggestions) { - if (suggestions.length === 0) { - return [{ - label: 'No Suggestions', - enabled: false, - }]; - } - const win = webcontents || remote.getCurrentWindow(); - return suggestions.map((s) => ({ - label: s, - click() { - (win.webContents || win.getWebContents()).replaceMisspelling(s); - }, - })); -} - -function getSpellCheckerLocaleMenus(onSelectSpellCheckerLocale) { - const currentLocale = ipcRenderer.sendSync('get-spellchecker-locale'); - const locales = [ - {language: 'English (UK)', locale: 'en-GB'}, - {language: 'English (US)', locale: 'en-US'}, - {language: 'French', locale: 'fr-FR'}, - {language: 'German', locale: 'de-DE'}, - {language: 'Polish', locale: 'pl-PL'}, - {language: 'Portuguese (BR)', locale: 'pt-BR'}, - {language: 'Russian', locale: 'ru-RU'}, - {language: 'Ukrainian', locale: 'uk-UA'}, - {language: 'Spanish (ES)', locale: 'es-ES'}, - {language: 'Spanish (MX)', locale: 'es-MX'}, - {language: 'Swedish', locale: 'sv-SE'}, - {language: 'Dutch', locale: 'nl-NL'}, - {language: 'Italian', locale: 'it-IT'}, - ]; - return locales.map((l) => ({ - label: l.language, - type: 'checkbox', - checked: l.locale === currentLocale, - click() { - if (onSelectSpellCheckerLocale) { - onSelectSpellCheckerLocale(l.locale); - } - }, - })); -} - -export default { - setup(options) { - const defaultOptions = { - useSpellChecker: false, - onSelectSpellCheckerLocale: null, - shouldShowMenu: (e, p) => { - const isInternalLink = p.linkURL.endsWith('#') && p.linkURL.slice(0, -1) === p.pageURL; - let isInternalSrc; - try { - const srcurl = urlUtils.parseURL(p.srcURL); - isInternalSrc = srcurl.protocol === 'file:'; - console.log(`srcrurl protocol: ${srcurl.protocol}`); - } catch (err) { - console.log(`ups: ${err}`); - isInternalSrc = false; - } - return p.isEditable || (p.mediaType !== 'none' && !isInternalSrc) || (p.linkURL !== '' && !isInternalLink) || p.misspelledWord !== '' || p.selectionText !== ''; - } - }; - const actualOptions = Object.assign({}, defaultOptions, options); - - electronContextMenu({ - prepend(_defaultActions, params) { - if (actualOptions.useSpellChecker) { - const prependMenuItems = []; - if (params.isEditable && params.misspelledWord !== '') { - const suggestions = ipcRenderer.sendSync('get-spelling-suggestions', params.misspelledWord); - prependMenuItems.push(...getSuggestionsMenus(options.window, suggestions)); - } - if (params.isEditable) { - prependMenuItems.push( - {type: 'separator'}, - {label: 'Spelling Languages', submenu: getSpellCheckerLocaleMenus(actualOptions.onSelectSpellCheckerLocale)}); - } - return prependMenuItems; - } - return []; - }, - ...actualOptions, - }); - }, -}; diff --git a/src/browser/js/notification.js b/src/browser/js/notification.js deleted file mode 100644 index 51012530..00000000 --- a/src/browser/js/notification.js +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -'use strict'; - -const OriginalNotification = Notification; -import {throttle} from 'underscore'; -import {ipcRenderer, remote} from 'electron'; - -import osVersion from '../../common/osVersion'; - -import ding from '../../assets/sounds/ding.mp3'; -import bing from '../../assets/sounds/bing.mp3'; -import crackle from '../../assets/sounds/crackle.mp3'; -import down from '../../assets/sounds/down.mp3'; -import hello from '../../assets/sounds/hello.mp3'; -import ripple from '../../assets/sounds/ripple.mp3'; -import upstairs from '../../assets/sounds/upstairs.mp3'; - -const DEFAULT_WIN7 = 'Ding'; -const notificationSounds = new Map([ - [DEFAULT_WIN7, ding], - ['Bing', bing], - ['Crackle', crackle], - ['Down', down], - ['Hello', hello], - ['Ripple', ripple], - ['Upstairs', upstairs], -]); - -const appIconURL = `file:///${remote.app.getAppPath()}/assets/appicon_48.png`; - -const playSound = throttle((soundName) => { - const audio = new Audio(notificationSounds.get(soundName)); - audio.play(); -}, 3000, {trailing: false}); - -export default class EnhancedNotification extends OriginalNotification { - constructor(title, options) { - if (process.platform === 'win32') { - // Replace with application icon. - options.icon = appIconURL; - } else if (process.platform === 'darwin') { - // Notification Center shows app's icon, so there were two icons on the notification. - Reflect.deleteProperty(options, 'icon'); - } - - const isWin7 = (process.platform === 'win32' && osVersion.isLowerThanOrEqualWindows8_1() && DEFAULT_WIN7); - const customSound = !options.silent && ((options.data && options.data.soundName !== 'None' && options.data.soundName) || isWin7); - - if (customSound) { - // disable native sound - options.silent = true; - } - - super(title, options); - - ipcRenderer.send('notified', { - title, - options, - }); - - if (customSound) { - playSound(customSound); - } - } - - set onclick(handler) { - super.onclick = () => { - const currentWindow = remote.getCurrentWindow(); - if (process.platform === 'win32') { - // show() breaks Aero Snap state. - if (currentWindow.isVisible()) { - currentWindow.focus(); - } else if (currentWindow.isMinimized()) { - currentWindow.restore(); - } else { - currentWindow.show(); - } - } else if (currentWindow.isMinimized()) { - currentWindow.restore(); - } else { - currentWindow.show(); - } - ipcRenderer.sendToHost('onNotificationClick'); - handler(); - }; - } - - get onclick() { - return super.onclick; - } -} diff --git a/src/browser/settings.html b/src/browser/settings.html deleted file mode 100644 index 03be9a29..00000000 --- a/src/browser/settings.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Settings - - - - - - -
- - - - diff --git a/src/browser/settings.jsx b/src/browser/settings.jsx deleted file mode 100644 index 47c89ac4..00000000 --- a/src/browser/settings.jsx +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import {remote, ipcRenderer} from 'electron'; - -window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval - throw new Error(`Sorry, ${remote.app.name} does not support window.eval() for security reasons.`); -}; - -import React from 'react'; -import ReactDOM from 'react-dom'; - -import Config from '../common/config'; - -import SettingsPage from './components/SettingsPage.jsx'; -import contextMenu from './js/contextMenu'; - -contextMenu.setup(); - -const config = new Config(remote.app.getPath('userData') + '/config.json', remote.getCurrentWindow().registryConfigData); - -function getDarkMode() { - if (process.platform !== 'darwin') { - return config.darkMode; - } - return null; -} - -function setDarkMode() { - if (process.platform !== 'darwin') { - const darkMode = Boolean(config.darkMode); - config.set('darkMode', !darkMode); - return !darkMode; - } - return null; -} - -function openMenu() { - if (process.platform !== 'darwin') { - ipcRenderer.send('open-app-menu'); - } -} - -ReactDOM.render( - , - document.getElementById('content') -); - -// Deny drag&drop navigation in mainWindow. -document.addEventListener('dragover', (event) => event.preventDefault()); -document.addEventListener('drop', (event) => event.preventDefault()); diff --git a/src/browser/updater.jsx b/src/browser/updater.jsx deleted file mode 100644 index 9e3eeb50..00000000 --- a/src/browser/updater.jsx +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import ReactDOM from 'react-dom'; -import propTypes from 'prop-types'; -import {ipcRenderer, remote} from 'electron'; - -import urlUtils from '../utils/url'; - -import UpdaterPage from './components/UpdaterPage.jsx'; - -const thisURL = urlUtils.parseURL(location.href); -const notifyOnly = thisURL.searchParams.get('notifyOnly') === 'true'; - -class UpdaterPageContainer extends React.Component { - constructor(props) { - super(props); - this.state = props.initialState; - } - - getTabWebContents() { - return remote.webContents.getFocusedWebContents(); - } - - componentDidMount() { - ipcRenderer.on('start-download', () => { - this.setState({ - isDownloading: true, - }); - }); - ipcRenderer.on('progress', (event, progress) => { - this.setState({ - progress, - }); - }); - ipcRenderer.on('zoom-in', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - if (activeTabWebContents.zoomLevel >= 9) { - return; - } - activeTabWebContents.zoomLevel += 1; - }); - - ipcRenderer.on('zoom-out', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - if (activeTabWebContents.zoomLevel <= -8) { - return; - } - activeTabWebContents.zoomLevel -= 1; - }); - - ipcRenderer.on('zoom-reset', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.zoomLevel = 0; - }); - - ipcRenderer.on('undo', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.undo(); - }); - - ipcRenderer.on('redo', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.redo(); - }); - - ipcRenderer.on('cut', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.cut(); - }); - - ipcRenderer.on('copy', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.copy(); - }); - - ipcRenderer.on('paste', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.paste(); - }); - - ipcRenderer.on('paste-and-match', () => { - const activeTabWebContents = this.getTabWebContents(); - if (!activeTabWebContents) { - return; - } - activeTabWebContents.pasteAndMatchStyle(); - }); - } - - render() { - return ( - { - ipcRenderer.send('click-release-notes'); - }} - onClickSkip={() => { - ipcRenderer.send('click-skip'); - }} - onClickRemind={() => { - ipcRenderer.send('click-remind'); - }} - onClickInstall={() => { - ipcRenderer.send('click-install'); - }} - onClickDownload={() => { - ipcRenderer.send('click-download'); - }} - onClickCancel={() => { - ipcRenderer.send('click-cancel'); - }} - /> - ); - } -} - -UpdaterPageContainer.propTypes = { - notifyOnly: propTypes.bool, - initialState: propTypes.object, -}; - -ReactDOM.render( - , - document.getElementById('content') -); diff --git a/src/browser/webview/mattermost.js b/src/browser/webview/mattermost.js deleted file mode 100644 index 5b68f365..00000000 --- a/src/browser/webview/mattermost.js +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -'use strict'; - -/* eslint-disable no-magic-numbers */ - -import {ipcRenderer, webFrame, remote} from 'electron'; - -const UNREAD_COUNT_INTERVAL = 1000; -const CLEAR_CACHE_INTERVAL = 6 * 60 * 60 * 1000; // 6 hours - -Reflect.deleteProperty(global.Buffer); // http://electron.atom.io/docs/tutorial/security/#buffer-global - -function isReactAppInitialized() { - const initializedRoot = - document.querySelector('#root.channel-view') || // React 16 webapp - document.querySelector('#root .signup-team__container') || // React 16 login - document.querySelector('div[data-reactroot]'); // Older React apps - if (initializedRoot === null) { - return false; - } - return initializedRoot.children.length !== 0; -} - -function watchReactAppUntilInitialized(callback) { - let count = 0; - const interval = 500; - const timeout = 30000; - const timer = setInterval(() => { - count += interval; - if (isReactAppInitialized() || count >= timeout) { // assumed as webapp has been initialized. - clearTimeout(timer); - callback(); - } - }, interval); -} - -window.addEventListener('load', () => { - if (document.getElementById('root') === null) { - console.log('The guest is not assumed as mattermost-webapp'); - ipcRenderer.sendToHost('onGuestInitialized'); - return; - } - watchReactAppUntilInitialized(() => { - ipcRenderer.sendToHost('onGuestInitialized', window.basename); - }); -}); - -// Sent for drag and drop tabs to work properly -document.addEventListener('mousemove', (event) => { - ipcRenderer.sendToHost('mouse-move', {clientX: event.clientX, clientY: event.clientY}); -}); - -document.addEventListener('mouseup', () => { - ipcRenderer.sendToHost('mouse-up'); -}); - -// listen for messages from the webapp -window.addEventListener('message', ({origin, data: {type, message = {}} = {}} = {}) => { - if (origin !== window.location.origin) { - return; - } - switch (type) { - case 'webapp-ready': { - // register with the webapp to enable custom integration functionality - window.postMessage( - { - type: 'register-desktop', - message: { - version: remote.app.getVersion(), - }, - }, - window.location.origin || '*' - ); - break; - } - case 'dispatch-notification': { - const {title, body, channel, teamId, silent, data} = message; - ipcRenderer.sendToHost('dispatchNotification', title, body, channel, teamId, silent, data, () => handleNotificationClick({teamId, channel})); - break; - } - } -}); - -const handleNotificationClick = ({channel, teamId}) => { - window.postMessage( - { - type: 'notification-clicked', - message: { - channel, - teamId, - }, - }, - window.location.origin - ); -}; - -ipcRenderer.on('notification-clicked', (event, {channel, teamId}) => { - handleNotificationClick({channel, teamId}); -}); - -function hasClass(element, className) { - const rclass = /[\t\r\n\f]/g; - if ((' ' + element.className + ' ').replace(rclass, ' ').indexOf(className) > -1) { - return true; - } - return false; -} - -function getUnreadCount() { - if (!this.unreadCount) { - this.unreadCount = 0; - } - if (!this.mentionCount) { - this.mentionCount = 0; - } - - // LHS not found => Log out => Count should be 0, but session may be expired. - if (document.getElementById('sidebar-left') === null) { - const extraParam = (new URLSearchParams(window.location.search)).get('extra'); - const sessionExpired = extraParam === 'expired'; - - ipcRenderer.sendToHost('onBadgeChange', sessionExpired, 0, 0, false, false); - this.sessionExpired = sessionExpired; - this.unreadCount = 0; - this.mentionCount = 0; - setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); - return; - } - - // unreadCount in sidebar - // Note: the active channel doesn't have '.unread-title'. - let unreadCount = document.getElementsByClassName('unread-title').length; - - // unreadCount in team sidebar - const teamSideBar = document.getElementsByClassName('team-sidebar'); // team-sidebar doesn't have id - if (teamSideBar.length === 1) { - unreadCount += teamSideBar[0].getElementsByClassName('unread').length; - } - - // mentionCount in sidebar - const elem = document.querySelectorAll('#sidebar-left .badge, #channel_view .badge'); - let mentionCount = 0; - for (let i = 0; i < elem.length; i++) { - if (isElementVisible(elem[i]) && !hasClass(elem[i], 'badge-notify')) { - mentionCount += Number(elem[i].innerHTML); - } - } - - const postAttrName = 'data-reactid'; - const lastPostElem = document.querySelector('div[' + postAttrName + '="' + this.lastCheckedPostId + '"]'); - let isUnread = false; - let isMentioned = false; - if (lastPostElem === null || !isElementVisible(lastPostElem)) { - // When load channel or change channel, this.lastCheckedPostId is invalid. - // So we get latest post and save lastCheckedPostId. - - // find active post-list. - const postLists = document.querySelectorAll('div.post-list__content'); - if (postLists.length === 0) { - setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); - return; - } - let post = null; - for (let j = 0; j < postLists.length; j++) { - if (isElementVisible(postLists[j])) { - post = postLists[j].children[0]; - } - } - if (post === null) { - setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); - return; - } - - // find latest post and save. - post = post.nextSibling; - while (post) { - if (post.nextSibling === null) { - if (post.getAttribute(postAttrName) !== null) { - this.lastCheckedPostId = post.getAttribute(postAttrName); - } - } - post = post.nextSibling; - } - } else if (lastPostElem !== null) { - let newPostElem = lastPostElem.nextSibling; - while (newPostElem) { - this.lastCheckedPostId = newPostElem.getAttribute(postAttrName); - isUnread = true; - const activeChannel = document.querySelector('.active .sidebar-channel'); - const closeButton = activeChannel.getElementsByClassName('btn-close'); - if (closeButton.length === 1 && closeButton[0].getAttribute('aria-describedby') === 'remove-dm-tooltip') { - // If active channel is DM, all posts is treated as mention. - isMentioned = true; - break; - } else { - // If active channel is public/private channel, only mentioned post is treated as mention. - const highlight = newPostElem.getElementsByClassName('mention-highlight'); - if (highlight.length !== 0 && isElementVisible(highlight[0])) { - isMentioned = true; - break; - } - } - newPostElem = newPostElem.nextSibling; - } - } - - if (this.sessionExpired || this.unreadCount !== unreadCount || this.mentionCount !== mentionCount || isUnread || isMentioned) { - ipcRenderer.sendToHost('onBadgeChange', false, unreadCount, mentionCount, isUnread, isMentioned); - } - this.unreadCount = unreadCount; - this.mentionCount = mentionCount; - this.sessionExpired = false; - setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); -} -setTimeout(getUnreadCount, UNREAD_COUNT_INTERVAL); - -function isElementVisible(elem) { - return elem.offsetHeight !== 0; -} - -function resetMisspelledState() { - ipcRenderer.once('spellchecker-is-ready', () => { - const element = document.activeElement; - if (element) { - element.blur(); - element.focus(); - } - }); - ipcRenderer.send('reply-on-spellchecker-is-ready'); -} - -function setSpellChecker() { - const spellCheckerLocale = ipcRenderer.sendSync('get-spellchecker-locale'); - webFrame.setSpellCheckProvider(spellCheckerLocale, { - spellCheck(words, callback) { - const misspeltWords = words.filter((text) => { - const res = ipcRenderer.sendSync('checkspell', text); - const isCorrect = (res === null) ? true : res; - return !isCorrect; - }); - callback(misspeltWords); - }, - }); - resetMisspelledState(); -} -setSpellChecker(); -ipcRenderer.on('set-spellchecker', setSpellChecker); - -// push user activity updates to the webapp -ipcRenderer.on('user-activity-update', (event, {userIsActive, isSystemEvent}) => { - if (window.location.origin !== 'null') { - window.postMessage({type: 'user-activity-update', message: {userIsActive, manual: isSystemEvent}}, window.location.origin); - } -}); - -// exit fullscreen embedded elements like youtube - https://mattermost.atlassian.net/browse/MM-19226 -ipcRenderer.on('exit-fullscreen', () => { - if (document.fullscreenElement && document.fullscreenElement.nodeName.toLowerCase() === 'iframe') { - document.exitFullscreen(); - } -}); - -// mattermost-webapp is SPA. So cache is not cleared due to no navigation. -// We needed to manually clear cache to free memory in long-term-use. -// http://seenaburns.com/debugging-electron-memory-usage/ -setInterval(() => { - webFrame.clearCache(); -}, CLEAR_CACHE_INTERVAL); - -/* eslint-enable no-magic-numbers */ diff --git a/src/common/JsonFileManager.js b/src/common/JsonFileManager.js index 7d1b038a..92aea6b3 100644 --- a/src/common/JsonFileManager.js +++ b/src/common/JsonFileManager.js @@ -4,34 +4,36 @@ import fs from 'fs'; export default class JsonFileManager { - constructor(file) { - this.jsonFile = file; - try { - this.json = JSON.parse(fs.readFileSync(file, 'utf-8')); - } catch (err) { - this.json = {}; + constructor(file) { + this.jsonFile = file; + try { + this.json = JSON.parse(fs.readFileSync(file, 'utf-8')); + } catch (err) { + this.json = {}; + } } - } - writeToFile() { - fs.writeFile(this.jsonFile, JSON.stringify(this.json, null, 2), (err) => { - if (err) { - console.error(err); - } - }); - } + writeToFile() { + fs.writeFile(this.jsonFile, JSON.stringify(this.json, null, 2), (err) => { + if (err) { + // No real point in bringing electron-log into this otherwise electron-free file + // eslint-disable-next-line no-console + console.error(err); + } + }); + } - setJson(json) { - this.json = json; - this.writeToFile(); - } + setJson(json) { + this.json = json; + this.writeToFile(); + } - setValue(key, value) { - this.json[key] = value; - this.writeToFile(); - } + setValue(key, value) { + this.json[key] = value; + this.writeToFile(); + } - getValue(key) { - return this.json[key]; - } + getValue(key) { + return this.json[key]; + } } diff --git a/src/common/communication.js b/src/common/communication.js new file mode 100644 index 00000000..29f0adb1 --- /dev/null +++ b/src/common/communication.js @@ -0,0 +1,83 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +export const SWITCH_SERVER = 'switch-server'; +export const SET_SERVER_KEY = 'set-server-key'; +export const MARK_READ = 'mark-read'; +export const FOCUS_BROWSERVIEW = 'focus-browserview'; +export const ZOOM = 'zoom'; +export const UNDO = 'undo'; +export const REDO = 'redo'; +export const HISTORY = 'history'; + +export const QUIT = 'quit'; + +export const GET_CONFIGURATION = 'get-configuration'; +export const UPDATE_CONFIGURATION = 'update-configuration'; +export const GET_LOCAL_CONFIGURATION = 'get-local-configuration'; +export const RELOAD_CONFIGURATION = 'reload-config'; + +export const UPDATE_TEAMS = 'update-teams'; +export const DARK_MODE_CHANGE = 'dark_mode_change'; +export const USER_ACTIVITY_UPDATE = 'user-activity-update'; + +export const LOAD_RETRY = 'load_retry'; +export const LOAD_SUCCESS = 'load_success'; +export const LOAD_FAILED = 'load_fail'; + +export const MAXIMIZE_CHANGE = 'maximized_change'; + +export const OPEN_EXTERNAL = 'open_external'; + +export const DOUBLE_CLICK_ON_WINDOW = 'double_click'; + +export const SHOW_NEW_SERVER_MODAL = 'show_new_server_modal'; + +export const RETRIEVE_MODAL_INFO = 'retrieve-modal-info'; +export const MODAL_INFO = 'modal-info'; +export const MODAL_CANCEL = 'modal-cancel'; +export const MODAL_RESULT = 'modal-result'; +export const MODAL_SEND_IPC_MESSAGE = 'modal-send-ipc-message'; +export const MODAL_OPEN = 'modal-open'; +export const MODAL_CLOSE = 'modal-close'; +export const NOTIFY_MENTION = 'notify_mention'; +export const WINDOW_CLOSE = 'window_close'; +export const WINDOW_MINIMIZE = 'window_minimize'; +export const WINDOW_MAXIMIZE = 'window_maximize'; +export const WINDOW_RESTORE = 'window_restore'; + +export const UPDATE_TARGET_URL = 'update_target_url'; + +export const PLAY_SOUND = 'play_sound'; + +export const GET_DOWNLOAD_LOCATION = 'get_download_location'; + +export const FOUND_IN_PAGE = 'found-in-page'; +export const FIND_IN_PAGE = 'find-in-page'; +export const STOP_FIND_IN_PAGE = 'stop-find-in-page'; +export const CLOSE_FINDER = 'close-finder'; +export const FOCUS_FINDER = 'focus-finder'; + +export const UPDATE_MENTIONS = 'update_mentions'; +export const IS_UNREAD = 'is_unread'; +export const UNREAD_RESULT = 'unread_result'; +export const SESSION_EXPIRED = 'session_expired'; +export const UPDATE_TRAY = 'update_tray'; +export const UPDATE_BADGE = 'update_badge'; + +export const SET_SERVER_NAME = 'set-server-name'; +export const REACT_APP_INITIALIZED = 'react-app-initialized'; + +export const TOGGLE_BACK_BUTTON = 'toggle-back-button'; + +export const SHOW_SETTINGS_WINDOW = 'show-settings-window'; + +export const RECEIVED_LOADING_SCREEN_DATA = 'received-loading-screen-data'; +export const GET_LOADING_SCREEN_DATA = 'get-loading-screen-data'; +export const LOADING_SCREEN_ANIMATION_FINISHED = 'loading-screen-animation-finished'; +export const TOGGLE_LOADING_SCREEN_VISIBILITY = 'toggle-loading-screen-visibility'; + +export const SELECT_NEXT_TAB = 'select-next-tab'; +export const SELECT_PREVIOUS_TAB = 'select-previous-tab'; +export const ADD_SERVER = 'add-server'; +export const FOCUS_THREE_DOT_MENU = 'focus-three-dot-menu'; diff --git a/src/common/config/RegistryConfig.js b/src/common/config/RegistryConfig.js index 4af09019..fc56a969 100644 --- a/src/common/config/RegistryConfig.js +++ b/src/common/config/RegistryConfig.js @@ -3,135 +3,144 @@ // See LICENSE.txt for license information. import {EventEmitter} from 'events'; - -import WindowsRegistry from 'winreg'; +import log from 'electron-log'; +import WindowsRegistry from 'winreg-utf8'; const REGISTRY_HIVE_LIST = [WindowsRegistry.HKLM, WindowsRegistry.HKCU]; const BASE_REGISTRY_KEY_PATH = '\\Software\\Policies\\Mattermost'; +export const REGISTRY_READ_EVENT = 'registry-read'; /** * Handles loading config data from the Windows registry set manually or by GPO */ export default class RegistryConfig extends EventEmitter { - constructor() { - super(); - this.initialized = false; - this.data = { - teams: [], - }; - } + constructor() { + super(); + this.initialized = false; + this.data = { + teams: [], + }; + } - /** + /** * Triggers loading data from Windows registry, supports async/await * * @emits {update} emitted once all data has been loaded from the registry */ - async init() { - if (process.platform === 'win32') { - // extract DefaultServerList from the registry - try { - const servers = await this.getServersListFromRegistry(); - if (servers.length) { - this.data.teams.push(...servers); - } - } catch (error) { - console.log('[RegistryConfig] Nothing retrieved for \'DefaultServerList\'', error); - } + async init() { + if (process.platform === 'win32') { + // extract DefaultServerList from the registry + try { + const servers = await this.getServersListFromRegistry(); + if (servers.length) { + this.data.teams.push(...servers); + } + } catch (error) { + log.warn('[RegistryConfig] Nothing retrieved for \'DefaultServerList\'', error); + } - // extract EnableServerManagement from the registry - try { - const enableServerManagement = await this.getEnableServerManagementFromRegistry(); - if (enableServerManagement !== null) { - this.data.enableServerManagement = enableServerManagement; - } - } catch (error) { - console.log('[RegistryConfig] Nothing retrieved for \'EnableServerManagement\'', error); - } + // extract EnableServerManagement from the registry + try { + const enableServerManagement = await this.getEnableServerManagementFromRegistry(); + if (enableServerManagement !== null) { + this.data.enableServerManagement = enableServerManagement; + } + } catch (error) { + log.warn('[RegistryConfig] Nothing retrieved for \'EnableServerManagement\'', error); + } - // extract EnableAutoUpdater from the registry - try { - const enableAutoUpdater = await this.getEnableAutoUpdatorFromRegistry(); - if (enableAutoUpdater !== null) { - this.data.enableAutoUpdater = enableAutoUpdater; + // extract EnableAutoUpdater from the registry + try { + const enableAutoUpdater = await this.getEnableAutoUpdatorFromRegistry(); + if (enableAutoUpdater !== null) { + this.data.enableAutoUpdater = enableAutoUpdater; + } + } catch (error) { + log.warn('[RegistryConfig] Nothing retrieved for \'EnableAutoUpdater\'', error); + } } - } catch (error) { - console.log('[RegistryConfig] Nothing retrieved for \'EnableAutoUpdater\'', error); - } + + // this will happen wether we are on windows and load the info or not + this.initialized = true; + this.emit(REGISTRY_READ_EVENT, this.data); } - this.initialized = true; - this.emit('update', this.data); - } - /** + /** * Extracts a list of servers */ - async getServersListFromRegistry() { - const defaultTeams = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`); - return defaultTeams.flat(2).reduce((teams, team) => { - if (team) { - teams.push({ - name: team.name, - url: team.value, - order: team.order, - }); - } - return teams; - }, []); - } + async getServersListFromRegistry() { + const defaultServers = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`); + return defaultServers.flat(2).reduce((servers, server, index) => { + if (server) { + servers.push({ + name: server.name, + url: server.value, + order: server.order || index, + }); + } + return servers; + }, []); + } - /** + /** * Determines whether server management has been enabled, disabled or isn't configured */ - async getEnableServerManagementFromRegistry() { - const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableServerManagement')); - const entry = entries.pop(); - return entry ? entry === '0x1' : null; - } + async getEnableServerManagementFromRegistry() { + const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableServerManagement')); + const entry = entries.pop(); + return entry ? entry === '0x1' : null; + } - /** + /** * Determines whether the auto updated has been enabled, disabled or isn't configured */ - async getEnableAutoUpdatorFromRegistry() { - const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableAutoUpdater')); - const entry = entries.pop(); - return entry ? entry === '0x1' : null; - } + async getEnableAutoUpdatorFromRegistry() { + const entries = (await this.getRegistryEntry(BASE_REGISTRY_KEY_PATH, 'EnableAutoUpdater')); + const entry = entries.pop(); + return entry ? entry === '0x1' : null; + } - /** + /** * Initiates retrieval of a specific key in the Windows registry * * @param {string} key Path to the registry key to return * @param {string} name Name of specific entry in the registry key to retrieve (optional) */ - async getRegistryEntry(key, name) { - const results = []; - for (const hive of REGISTRY_HIVE_LIST) { - results.push(this.getRegistryEntryValues(new WindowsRegistry({hive, key}), name)); + async getRegistryEntry(key, name) { + const results = []; + for (const hive of REGISTRY_HIVE_LIST) { + results.push(this.getRegistryEntryValues(hive, key, name)); + } + const entryValues = await Promise.all(results); + return entryValues.filter((value) => value); } - const entryValues = await Promise.all(results); - return entryValues.filter((value) => value); - } - /** + /** * Handles actual retrieval of entries from a configured WindowsRegistry instance * * @param {WindowsRegistry} regKey A configured instance of the WindowsRegistry class * @param {string} name Name of the specific entry to retrieve (optional) */ - getRegistryEntryValues(regKey, name) { - return new Promise((resolve) => { - regKey.values((error, items) => { - if (error || !items || !items.length) { - resolve(); - return; - } - if (name) { // looking for a single entry value - const registryItem = items.find((item) => item.name === name); - resolve(registryItem && registryItem.value ? registryItem.value : null); - } else { // looking for an entry list - resolve(items); - } - }); - }); - } + getRegistryEntryValues(hive, key, name) { + const registry = new WindowsRegistry({hive, key, utf8: true}); + return new Promise((resolve, reject) => { + try { + registry.values((error, results) => { + if (error || !results || results.length === 0) { + resolve(); + return; + } + if (name) { // looking for a single entry value + const registryItem = results.find((item) => item.name === name); + resolve(registryItem && registryItem.value ? registryItem.value : null); + } else { // looking for an entry list + resolve(results); + } + }); + } catch (e) { + log.error(`There was an error accessing the registry for ${key}`); + reject(e); + } + }); + } } diff --git a/src/common/config/buildConfig.js b/src/common/config/buildConfig.js index bf4b0a65..c5910c78 100644 --- a/src/common/config/buildConfig.js +++ b/src/common/config/buildConfig.js @@ -18,16 +18,16 @@ * @prop {[]} managedResources - Defines which paths are managed */ const buildConfig = { - defaultTeams: [/* + defaultTeams: [/* { name: 'example', url: 'https://example.com' } */], - helpLink: 'https://about.mattermost.com/default-desktop-app-documentation/', - enableServerManagement: true, - enableAutoUpdater: true, - managedResources: ['trusted'], + helpLink: 'https://about.mattermost.com/default-desktop-app-documentation/', + enableServerManagement: true, + enableAutoUpdater: true, + managedResources: ['trusted'], }; export default buildConfig; diff --git a/src/common/config/defaultPreferences.js b/src/common/config/defaultPreferences.js index d7b5cff5..ed30a0f3 100644 --- a/src/common/config/defaultPreferences.js +++ b/src/common/config/defaultPreferences.js @@ -7,23 +7,23 @@ * @param {number} version - Scheme version. (Not application version) */ const defaultPreferences = { - version: 2, - teams: [], - showTrayIcon: true, - trayIconTheme: 'light', - minimizeToTray: true, - notifications: { - flashWindow: 2, - bounceIcon: true, - bounceIconType: 'informational', - }, - showUnreadBadge: true, - useSpellChecker: true, - enableHardwareAcceleration: true, - autostart: true, - spellCheckerLocale: 'en-US', - darkMode: false, - downloadLocation: `/Users/${process.env.USER || process.env.USERNAME}/Downloads` + version: 2, + teams: [], + showTrayIcon: true, + trayIconTheme: 'light', + minimizeToTray: true, + notifications: { + flashWindow: 2, + bounceIcon: true, + bounceIconType: 'informational', + }, + showUnreadBadge: true, + useSpellChecker: true, + enableHardwareAcceleration: true, + autostart: true, + spellCheckerLocale: 'en-US', + darkMode: false, + downloadLocation: `/Users/${process.env.USER || process.env.USERNAME}/Downloads`, }; export default defaultPreferences; diff --git a/src/common/config/index.js b/src/common/config/index.js index 81bd139d..043ca541 100644 --- a/src/common/config/index.js +++ b/src/common/config/index.js @@ -1,397 +1,469 @@ // Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. - import fs from 'fs'; + import path from 'path'; import {EventEmitter} from 'events'; +import {ipcMain, nativeTheme, app} from 'electron'; +import log from 'electron-log'; import * as Validator from '../../main/Validator'; +import {UPDATE_TEAMS, GET_CONFIGURATION, UPDATE_CONFIGURATION, GET_LOCAL_CONFIGURATION} from 'common/communication'; + import defaultPreferences from './defaultPreferences'; import upgradeConfigData from './upgradePreferences'; import buildConfig from './buildConfig'; +import RegistryConfig, {REGISTRY_READ_EVENT} from './RegistryConfig'; /** * Handles loading and merging all sources of configuration as well as saving user provided config */ export default class Config extends EventEmitter { - constructor(configFilePath, registryConfigData = {teams: []}) { - super(); - this.configFilePath = configFilePath; - this.registryConfigData = registryConfigData; - this.reload(); - } - - /** - * Reload all sources of config data - * - * @param {boolean} synchronize determines whether or not to emit a synchronize event once config has been reloaded - * @emits {update} emitted once all data has been loaded and merged - * @emits {synchronize} emitted when requested by a call to method; used to notify other config instances of changes - */ - reload(synchronize = false) { - this.defaultConfigData = this.loadDefaultConfigData(); - this.buildConfigData = this.loadBuildConfigData(); - - this.localConfigData = this.loadLocalConfigFile(); - this.localConfigData = this.checkForConfigUpdates(this.localConfigData); - - this.regenerateCombinedConfigData(); - - this.emit('update', this.combinedData); - - if (synchronize) { - this.emit('synchronize'); + constructor(configFilePath) { + super(); + this.configFilePath = configFilePath; } - } - /** - * Used to save a single config property - * - * @param {string} key name of config property to be saved - * @param {*} data value to save for provided key - */ - set(key, data) { - if (key) { - this.localConfigData[key] = data; - this.regenerateCombinedConfigData(); - this.saveLocalConfigData(); + // separating constructor from init so main can setup event listeners + init = () => { + this.registryConfig = new RegistryConfig(); + this.registryConfig.once(REGISTRY_READ_EVENT, this.loadRegistry); + this.registryConfig.init(); } - } - /** - * Used to save an array of config properties in one go - * - * @param {array} properties an array of config properties to save - */ - setMultiple(properties = []) { - if (properties.length) { - properties.forEach(({key, data}) => { - if (key) { - this.localConfigData[key] = data; + /** + * Gets the teams from registry into the config object and reload + * + * @param {object} registryData Team configuration from the registry and if teams can be managed by user + */ + + loadRegistry = (registryData) => { + this.registryConfigData = registryData; + this.reload(); + ipcMain.handle(GET_CONFIGURATION, this.handleGetConfiguration); + ipcMain.handle(GET_LOCAL_CONFIGURATION, this.handleGetLocalConfiguration); + ipcMain.handle(UPDATE_TEAMS, this.handleUpdateTeams); + ipcMain.on(UPDATE_CONFIGURATION, this.setMultiple); + if (process.platform === 'darwin' || process.platform === 'win32') { + nativeTheme.on('updated', this.handleUpdateTheme); } - }); - this.regenerateCombinedConfigData(); - this.saveLocalConfigData(); } - } - setRegistryConfigData(registryConfigData = {teams: []}) { - this.registryConfigData = Object.assign({}, registryConfigData); - this.reload(); - } + /** + * Reload all sources of config data + * + * @param {boolean} synchronize determines whether or not to emit a synchronize event once config has been reloaded + * @emits {update} emitted once all data has been loaded and merged + * @emits {synchronize} emitted when requested by a call to method; used to notify other config instances of changes + */ + reload = () => { + this.defaultConfigData = this.loadDefaultConfigData(); + this.buildConfigData = this.loadBuildConfigData(); + this.localConfigData = this.loadLocalConfigFile(); + this.localConfigData = this.checkForConfigUpdates(this.localConfigData); + this.regenerateCombinedConfigData(); - /** - * Used to replace the existing config data with new config data - * - * @param {object} configData a new, config data object to completely replace the existing config data - */ - replace(configData) { - const newConfigData = configData; - - this.localConfigData = Object.assign({}, this.localConfigData, newConfigData); - - this.regenerateCombinedConfigData(); - this.saveLocalConfigData(); - } - - /** - * Used to save the current set of local config data to disk - * - * @emits {update} emitted once all data has been saved - * @emits {synchronize} emitted once all data has been saved; used to notify other config instances of changes - * @emits {error} emitted if saving local config data to file fails - */ - saveLocalConfigData() { - try { - this.writeFile(this.configFilePath, this.localConfigData, (error) => { - if (error) { - throw new Error(error); - } this.emit('update', this.combinedData); this.emit('synchronize'); - }); - } catch (error) { - this.emit('error', error); } - } - // getters for accessing the various config data inputs - - get data() { - return this.combinedData; - } - get localData() { - return this.localConfigData; - } - get defaultData() { - return this.defaultConfigData; - } - get buildData() { - return this.buildConfigData; - } - get registryData() { - return this.registryConfigData; - } - - // convenience getters - - get version() { - return this.combinedData.version; - } - get teams() { - return this.combinedData.teams; - } - get darkMode() { - return this.combinedData.darkMode; - } - get localTeams() { - return this.localConfigData.teams; - } - get predefinedTeams() { - return [...this.buildConfigData.defaultTeams, ...this.registryConfigData.teams]; - } - get enableHardwareAcceleration() { - return this.combinedData.enableHardwareAcceleration; - } - get enableServerManagement() { - return this.combinedData.enableServerManagement; - } - get enableAutoUpdater() { - return this.combinedData.enableAutoUpdater; - } - get autostart() { - return this.combinedData.autostart; - } - get notifications() { - return this.combinedData.notifications; - } - get showUnreadBadge() { - return this.combinedData.showUnreadBadge; - } - get useSpellChecker() { - return this.combinedData.useSpellChecker; - } - get spellCheckerLocale() { - return this.combinedData.spellCheckerLocale; - } - get showTrayIcon() { - return this.combinedData.showTrayIcon; - } - get trayIconTheme() { - return this.combinedData.trayIconTheme; - } - get helpLink() { - return this.combinedData.helpLink; - } - - // initialization/processing methods - - /** - * Returns a copy of the app's default config data - */ - loadDefaultConfigData() { - return this.copy(defaultPreferences); - } - - /** - * Returns a copy of the app's build config data - */ - loadBuildConfigData() { - return this.copy(buildConfig); - } - - /** - * Loads and returns locally stored config data from the filesystem or returns app defaults if no file is found - */ - loadLocalConfigFile() { - let configData = {}; - try { - configData = this.readFileSync(this.configFilePath); - - // validate based on config file version - if (configData.version > 1) { - configData = Validator.validateV2ConfigData(configData); - } else { - switch (configData.version) { - case 1: - configData = Validator.validateV1ConfigData(configData); - break; - default: - configData = Validator.validateV0ConfigData(configData); + /** + * Used to save a single config property + * + * @param {string} key name of config property to be saved + * @param {*} data value to save for provided key + */ + set = (key, data) => { + if (key) { + this.localConfigData[key] = data; + this.regenerateCombinedConfigData(); + this.saveLocalConfigData(); } - } - if (!configData) { - throw new Error('Provided configuration file does not validate, using defaults instead.'); - } - } catch (e) { - console.log('Failed to load configuration file from the filesystem. Using defaults.'); - configData = this.copy(this.defaultConfigData); - - // add default team to teams if one exists and there arent currently any teams - if (!configData.teams.length && this.defaultConfigData.defaultTeam) { - configData.teams.push(this.defaultConfigData.defaultTeam); - } - delete configData.defaultTeam; - - this.writeFileSync(this.configFilePath, configData); - } - return configData; - } - - /** - * Determines if locally stored data needs to be updated and upgrades as needed - * - * @param {*} data locally stored data - */ - checkForConfigUpdates(data) { - let configData = data; - try { - if (configData.version !== this.defaultConfigData.version) { - configData = upgradeConfigData(configData); - this.writeFileSync(this.configFilePath, configData); - console.log(`Configuration updated to version ${this.defaultConfigData.version} successfully.`); - } - } catch (error) { - console.log(`Failed to update configuration to version ${this.defaultConfigData.version}.`); - } - return configData; - } - - /** - * Properly combines all sources of data into a single, manageable set of all config data - */ - regenerateCombinedConfigData() { - // combine all config data in the correct order - this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.registryConfigData); - - // remove unecessary data pulled from default and build config - delete this.combinedData.defaultTeam; - delete this.combinedData.defaultTeams; - - // IMPORTANT: properly combine teams from all sources - let combinedTeams = []; - - // - start by adding default teams from buildConfig, if any - if (this.buildConfigData.defaultTeams && this.buildConfigData.defaultTeams.length) { - combinedTeams.push(...this.buildConfigData.defaultTeams); } - // - add registry defined teams, if any - if (this.registryConfigData.teams && this.registryConfigData.teams.length) { - combinedTeams.push(...this.registryConfigData.teams); + /** + * Used to save an array of config properties in one go + * + * @param {array} properties an array of config properties to save + */ + setMultiple = (event, properties = []) => { + if (properties.length) { + properties.forEach(({key, data}) => { + if (key) { + this.localConfigData[key] = data; + } + }); + this.regenerateCombinedConfigData(); + this.saveLocalConfigData(); + } + + return this.localConfigData; //this is the only part that changes } - // - add locally defined teams only if server management is enabled - if (this.enableServerManagement) { - combinedTeams.push(...this.localConfigData.teams); + setRegistryConfigData = (registryConfigData = {teams: []}) => { + this.registryConfigData = Object.assign({}, registryConfigData); + this.reload(); } - combinedTeams = this.filterOutDuplicateTeams(combinedTeams); - combinedTeams = this.sortUnorderedTeams(combinedTeams); + /** + * Used to replace the existing config data with new config data + * + * @param {object} configData a new, config data object to completely replace the existing config data + */ + replace = (configData) => { + const newConfigData = configData; - this.combinedData.teams = combinedTeams; - this.combinedData.localTeams = this.localConfigData.teams; - this.combinedData.buildTeams = this.buildConfigData.defaultTeams; - this.combinedData.registryTeams = this.registryConfigData.teams; - } + this.localConfigData = Object.assign({}, this.localConfigData, newConfigData); - /** - * Returns the provided list of teams with duplicates filtered out - * - * @param {array} teams array of teams to check for duplicates - */ - filterOutDuplicateTeams(teams) { - let newTeams = teams; - const uniqueURLs = new Set(); - newTeams = newTeams.filter((team) => { - return uniqueURLs.has(team.url) ? false : uniqueURLs.add(team.url); - }); - return newTeams; - } - - /** - * Returns the provided array fo teams with existing teams filtered out - * @param {array} teams array of teams to check for already defined teams - */ - filterOutPredefinedTeams(teams) { - let newTeams = teams; - - // filter out predefined teams - newTeams = newTeams.filter((newTeam) => { - return this.predefinedTeams.findIndex((existingTeam) => newTeam.url === existingTeam.url) === -1; // eslint-disable-line max-nested-callbacks - }); - - return newTeams; - } - - /** - * Apply a default sort order to the team list, if no order is specified. - * @param {array} teams to sort - */ - sortUnorderedTeams(teams) { - // We want to preserve the array order of teams in the config, otherwise a lot of bugs will occur - const mappedTeams = teams.map((team, index) => ({team, originalOrder: index})); - - // Make a best pass at interpreting sort order. If an order is not specified, assume it is 0. - // - const newTeams = mappedTeams.sort((x, y) => { - if (x.team.order == null) { - x.team.order = 0; - } - if (y.team.order == null) { - y.team.order = 0; - } - - // once we ensured `order` exists, we can sort numerically - return x.team.order - y.team.order; - }); - - // Now re-number all items from 0 to (max), ensuring user's sort order is preserved. The - // new tabbed interface requires an item with order:0 in order to raise the first tab. - // - newTeams.forEach((mappedTeam, i) => { - mappedTeam.team.order = i; - }); - - return newTeams.sort((x, y) => x.originalOrder - y.originalOrder).map((mappedTeam) => mappedTeam.team); - } - - // helper functions - - readFileSync(filePath) { - return JSON.parse(fs.readFileSync(filePath, 'utf8')); - } - - writeFile(filePath, configData, callback) { - if (configData.version !== this.defaultConfigData.version) { - throw new Error('version ' + configData.version + ' is not equal to ' + this.defaultConfigData.version); - } - const json = JSON.stringify(configData, null, ' '); - fs.writeFile(filePath, json, 'utf8', callback); - } - - writeFileSync(filePath, config) { - if (config.version !== this.defaultConfigData.version) { - throw new Error('version ' + config.version + ' is not equal to ' + this.defaultConfigData.version); + this.regenerateCombinedConfigData(); + this.saveLocalConfigData(); } - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); + /** + * Used to save the current set of local config data to disk + * + * @emits {update} emitted once all data has been saved + * @emits {synchronize} emitted once all data has been saved; used to notify other config instances of changes + * @emits {error} emitted if saving local config data to file fails + */ + saveLocalConfigData = () => { + try { + this.writeFile(this.configFilePath, this.localConfigData, (error) => { + if (error) { + throw new Error(error); + } + this.emit('update', this.combinedData); + this.emit('synchronize'); + }); + } catch (error) { + this.emit('error', error); + } } - const json = JSON.stringify(config, null, ' '); - fs.writeFileSync(filePath, json, 'utf8'); - } + // getters for accessing the various config data inputs - merge(base, target) { - return Object.assign({}, base, target); - } + get data() { + return this.combinedData; + } + get localData() { + return this.localConfigData; + } + get defaultData() { + return this.defaultConfigData; + } + get buildData() { + return this.buildConfigData; + } + get registryData() { + return this.registryConfigData; + } - copy(data) { - return Object.assign({}, data); - } + // convenience getters + + get version() { + return this.combinedData.version; + } + get teams() { + return this.combinedData.teams; + } + get darkMode() { + return this.combinedData.darkMode; + } + get localTeams() { + return this.localConfigData.teams; + } + get predefinedTeams() { + return [...this.buildConfigData.defaultTeams, ...this.registryConfigData.teams]; + } + get enableHardwareAcceleration() { + return this.combinedData.enableHardwareAcceleration; + } + get enableServerManagement() { + return this.combinedData.enableServerManagement; + } + get enableAutoUpdater() { + return this.combinedData.enableAutoUpdater; + } + get autostart() { + return this.combinedData.autostart; + } + get notifications() { + return this.combinedData.notifications; + } + get showUnreadBadge() { + return this.combinedData.showUnreadBadge; + } + get useSpellChecker() { + return this.combinedData.useSpellChecker; + } + get spellCheckerLocale() { + return this.combinedData.spellCheckerLocale; + } + get showTrayIcon() { + return this.combinedData.showTrayIcon; + } + get trayIconTheme() { + return this.combinedData.trayIconTheme; + } + get helpLink() { + return this.combinedData.helpLink; + } + + // initialization/processing methods + + /** + * Returns a copy of the app's default config data + */ + loadDefaultConfigData = () => { + return this.copy(defaultPreferences); + } + + /** + * Returns a copy of the app's build config data + */ + loadBuildConfigData = () => { + return this.copy(buildConfig); + } + + /** + * Loads and returns locally stored config data from the filesystem or returns app defaults if no file is found + */ + loadLocalConfigFile = () => { + let configData = {}; + try { + configData = this.readFileSync(this.configFilePath); + + // validate based on config file version + if (configData.version > 1) { + configData = Validator.validateV2ConfigData(configData); + } else { + switch (configData.version) { + case 1: + configData = Validator.validateV1ConfigData(configData); + break; + default: + configData = Validator.validateV0ConfigData(configData); + } + } + if (!configData) { + throw new Error('Provided configuration file does not validate, using defaults instead.'); + } + } catch (e) { + log.warn('Failed to load configuration file from the filesystem. Using defaults.'); + configData = this.copy(this.defaultConfigData); + + // add default team to teams if one exists and there arent currently any teams + if (!configData.teams.length && this.defaultConfigData.defaultTeam) { + configData.teams.push(this.defaultConfigData.defaultTeam); + } + delete configData.defaultTeam; + + this.writeFileSync(this.configFilePath, configData); + } + return configData; + } + + /** + * Determines if locally stored data needs to be updated and upgrades as needed + * + * @param {*} data locally stored data + */ + checkForConfigUpdates = (data) => { + let configData = data; + try { + if (configData.version !== this.defaultConfigData.version) { + configData = upgradeConfigData(configData); + this.writeFileSync(this.configFilePath, configData); + log.info(`Configuration updated to version ${this.defaultConfigData.version} successfully.`); + } + } catch (error) { + log.error(`Failed to update configuration to version ${this.defaultConfigData.version}.`); + } + return configData; + } + + /** + * Properly combines all sources of data into a single, manageable set of all config data + */ + regenerateCombinedConfigData = () => { + // combine all config data in the correct order + this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.registryConfigData); + + // remove unecessary data pulled from default and build config + delete this.combinedData.defaultTeam; + delete this.combinedData.defaultTeams; + + // IMPORTANT: properly combine teams from all sources + let combinedTeams = []; + + // - start by adding default teams from buildConfig, if any + if (this.buildConfigData.defaultTeams && this.buildConfigData.defaultTeams.length) { + combinedTeams.push(...this.buildConfigData.defaultTeams); + } + + // - add registry defined teams, if any + if (this.registryConfigData.teams && this.registryConfigData.teams.length) { + combinedTeams.push(...this.registryConfigData.teams); + } + + // - add locally defined teams only if server management is enabled + if (this.enableServerManagement) { + combinedTeams.push(...this.localConfigData.teams); + } + + combinedTeams = this.filterOutDuplicateTeams(combinedTeams); + combinedTeams = this.sortUnorderedTeams(combinedTeams); + + this.combinedData.teams = combinedTeams; + this.combinedData.localTeams = this.localConfigData.teams; + this.combinedData.buildTeams = this.buildConfigData.defaultTeams; + this.combinedData.registryTeams = this.registryConfigData.teams; + if (process.platform === 'darwin' || process.platform === 'win32') { + this.combinedData.darkMode = nativeTheme.shouldUseDarkColors; + } + this.combinedData.appName = app.name; + } + + /** + * Returns the provided list of teams with duplicates filtered out + * + * @param {array} teams array of teams to check for duplicates + */ + filterOutDuplicateTeams = (teams) => { + let newTeams = teams; + const uniqueURLs = new Set(); + newTeams = newTeams.filter((team) => { + return uniqueURLs.has(team.url) ? false : uniqueURLs.add(team.url); + }); + return newTeams; + } + + /** + * Returns the provided array fo teams with existing teams filtered out + * @param {array} teams array of teams to check for already defined teams + */ + filterOutPredefinedTeams = (teams) => { + let newTeams = teams; + + // filter out predefined teams + newTeams = newTeams.filter((newTeam) => { + return this.predefinedTeams.findIndex((existingTeam) => newTeam.url === existingTeam.url) === -1; // eslint-disable-line max-nested-callbacks + }); + + return newTeams; + } + + /** + * Apply a default sort order to the team list, if no order is specified. + * @param {array} teams to sort + */ + sortUnorderedTeams = (teams) => { + // We want to preserve the array order of teams in the config, otherwise a lot of bugs will occur + const mappedTeams = teams.map((team, index) => ({team, originalOrder: index})); + + // Make a best pass at interpreting sort order. If an order is not specified, assume it is 0. + // + const newTeams = mappedTeams.sort((x, y) => { + if (x.team.order == null) { + x.team.order = 0; + } + if (y.team.order == null) { + y.team.order = 0; + } + + // once we ensured `order` exists, we can sort numerically + return x.team.order - y.team.order; + }); + + // Now re-number all items from 0 to (max), ensuring user's sort order is preserved. The + // new tabbed interface requires an item with order:0 in order to raise the first tab. + // + newTeams.forEach((mappedTeam, i) => { + mappedTeam.team.order = i; + }); + + return newTeams.sort((x, y) => x.originalOrder - y.originalOrder).map((mappedTeam) => mappedTeam.team); + } + + // helper functions + + readFileSync = (filePath) => { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } + + writeFile = (filePath, configData, callback) => { + if (configData.version !== this.defaultConfigData.version) { + throw new Error('version ' + configData.version + ' is not equal to ' + this.defaultConfigData.version); + } + const json = JSON.stringify(configData, null, ' '); + fs.writeFile(filePath, json, 'utf8', callback); + } + + writeFileSync = (filePath, config) => { + if (config.version !== this.defaultConfigData.version) { + throw new Error('version ' + config.version + ' is not equal to ' + this.defaultConfigData.version); + } + + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + + const json = JSON.stringify(config, null, ' '); + fs.writeFileSync(filePath, json, 'utf8'); + } + + merge = (base, target) => { + return Object.assign({}, base, target); + } + + copy = (data) => { + return Object.assign({}, data); + } + + handleGetConfiguration = (event, option) => { + const config = {...this.combinedData}; + if (option) { + return config[option]; + } + return config; + } + + handleGetLocalConfiguration = (event, option) => { + const config = {...this.localConfigData}; + config.appName = app.name; + config.enableServerManagement = this.combinedData.enableServerManagement; + if (option) { + return config[option]; + } + return config; + } + + handleUpdateTeams = (event, newTeams) => { + this.set('teams', newTeams); + return this.combinedData.teams; + } + + /** + * Detects changes in darkmode if it is windows or osx, updates the config and propagates the changes + * @emits 'darkModeChange' + */ + handleUpdateTheme = () => { + if (this.combinedData.darkMode !== nativeTheme.shouldUseDarkColors) { + this.combinedData.darkMode = nativeTheme.shouldUseDarkColors; + this.emit('darkModeChange', this.combinedData.darkMode); + } + } + + /** + * Manually toggles dark mode for OSes that don't have a native dark mode setting + * @emits 'darkModeChange' + */ + toggleDarkModeManually = () => { + this.set('darkMode', !this.combinedData.darkMode); + this.emit('darkModeChange', this.combinedData.darkMode); + } } diff --git a/src/common/config/pastDefaultPreferences.js b/src/common/config/pastDefaultPreferences.js index b47b284d..c5fe6ac4 100644 --- a/src/common/config/pastDefaultPreferences.js +++ b/src/common/config/pastDefaultPreferences.js @@ -4,26 +4,26 @@ import defaultPreferences from './defaultPreferences'; const pastDefaultPreferences = { - 0: { - url: '', - }, - 1: { - version: 1, - teams: [], - showTrayIcon: false, - trayIconTheme: 'light', - minimizeToTray: false, - notifications: { - flashWindow: 0, - bounceIcon: false, - bounceIconType: 'informational', + 0: { + url: '', + }, + 1: { + version: 1, + teams: [], + showTrayIcon: false, + trayIconTheme: 'light', + minimizeToTray: false, + notifications: { + flashWindow: 0, + bounceIcon: false, + bounceIconType: 'informational', + }, + showUnreadBadge: true, + useSpellChecker: true, + enableHardwareAcceleration: true, + autostart: true, + spellCheckerLocale: 'en-US', }, - showUnreadBadge: true, - useSpellChecker: true, - enableHardwareAcceleration: true, - autostart: true, - spellCheckerLocale: 'en-US', - }, }; pastDefaultPreferences[`${defaultPreferences.version}`] = defaultPreferences; diff --git a/src/common/config/upgradePreferences.js b/src/common/config/upgradePreferences.js index 38ebadf2..2078caa6 100644 --- a/src/common/config/upgradePreferences.js +++ b/src/common/config/upgradePreferences.js @@ -4,39 +4,39 @@ import pastDefaultPreferences from './pastDefaultPreferences'; function deepCopy(object) { - return JSON.parse(JSON.stringify(object)); + return JSON.parse(JSON.stringify(object)); } function upgradeV0toV1(configV0) { - const config = deepCopy(pastDefaultPreferences['1']); - if (config.version !== 1) { - throw new Error('pastDefaultPreferences[\'1\'].version is not equal to 1'); - } - config.teams.push({ - name: 'Primary team', - url: configV0.url, - }); - return config; + const config = deepCopy(pastDefaultPreferences['1']); + if (config.version !== 1) { + throw new Error('pastDefaultPreferences[\'1\'].version is not equal to 1'); + } + config.teams.push({ + name: 'Primary team', + url: configV0.url, + }); + return config; } function upgradeV1toV2(configV1) { - const config = deepCopy(configV1); - config.version = 2; - config.teams.forEach((value, index) => { - value.order = index; - }); - config.darkMode = false; - return config; + const config = deepCopy(configV1); + config.version = 2; + config.teams.forEach((value, index) => { + value.order = index; + }); + config.darkMode = false; + return config; } export default function upgradeToLatest(config) { - const configVersion = config.version ? config.version : 0; - switch (configVersion) { - case 1: - return upgradeToLatest(upgradeV1toV2(config)); - case 0: - return upgradeToLatest(upgradeV0toV1(config)); - default: - return config; - } + const configVersion = config.version ? config.version : 0; + switch (configVersion) { + case 1: + return upgradeToLatest(upgradeV1toV2(config)); + case 0: + return upgradeToLatest(upgradeV0toV1(config)); + default: + return config; + } } diff --git a/src/common/deepmerge.js b/src/common/deepmerge.js index 5a947f40..c88fcf8c 100644 --- a/src/common/deepmerge.js +++ b/src/common/deepmerge.js @@ -4,5 +4,5 @@ import deepmerge from 'deepmerge'; export default function deepMergeProxy(x, y, options) { - return deepmerge(x, y, options); // due to webpack conversion + return deepmerge(x, y, options); // due to webpack conversion } diff --git a/src/common/osVersion.js b/src/common/osVersion.js index 1347d758..a1d9433b 100644 --- a/src/common/osVersion.js +++ b/src/common/osVersion.js @@ -7,14 +7,14 @@ import os from 'os'; const releaseSplit = os.release().split('.'); export default { - major: parseInt(releaseSplit[0], 10), - minor: parseInt(releaseSplit[1], 10), - isLowerThanOrEqualWindows8_1() { - if (process.platform !== 'win32') { - return false; - } + major: parseInt(releaseSplit[0], 10), + minor: parseInt(releaseSplit[1], 10), + isLowerThanOrEqualWindows8_1() { + if (process.platform !== 'win32') { + return false; + } - // consider Windows 7 and later. - return (this.major <= 6 && this.minor <= 3); - }, + // consider Windows 7 and later. + return (this.major <= 6 && this.minor <= 3); + }, }; diff --git a/src/common/permissions.js b/src/common/permissions.js index cdfc3487..3eb5ffea 100644 --- a/src/common/permissions.js +++ b/src/common/permissions.js @@ -11,5 +11,5 @@ export const BASIC_AUTH_PERMISSION = 'canBasicAuth'; // Permission descriptions export const PERMISSION_DESCRIPTION = { - [BASIC_AUTH_PERMISSION]: 'Web Authentication', + [BASIC_AUTH_PERMISSION]: 'Web Authentication', }; diff --git a/src/common/utils/constants.js b/src/common/utils/constants.js new file mode 100644 index 00000000..b39a9dee --- /dev/null +++ b/src/common/utils/constants.js @@ -0,0 +1,10 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +export const PRODUCTION = 'production'; +export const DEVELOPMENT = 'development'; + +export const SECOND = 1000; +export const RELOAD_INTERVAL = 10 * SECOND; + +export const MAX_SERVER_RETRIES = 5; diff --git a/src/common/utils/url.js b/src/common/utils/url.js new file mode 100644 index 00000000..16192c09 --- /dev/null +++ b/src/common/utils/url.js @@ -0,0 +1,249 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {isHttpsUri, isHttpUri, isUri} from 'valid-url'; + +import buildConfig from '../config/buildConfig'; + +// supported custom login paths (oath, saml) +const customLoginRegexPaths = [ + /^\/oauth\/authorize$/i, + /^\/oauth\/deauthorize$/i, + /^\/oauth\/access_token$/i, + /^\/oauth\/[A-Za-z0-9]+\/complete$/i, + /^\/oauth\/[A-Za-z0-9]+\/login$/i, + /^\/oauth\/[A-Za-z0-9]+\/signup$/i, + /^\/api\/v3\/oauth\/[A-Za-z0-9]+\/complete$/i, + /^\/signup\/[A-Za-z0-9]+\/complete$/i, + /^\/login\/[A-Za-z0-9]+\/complete$/i, + /^\/login\/sso\/saml$/i, +]; + +function getDomain(inputURL) { + const parsedURL = parseURL(inputURL); + return parsedURL.origin; +} + +function isValidURL(testURL) { + return Boolean(isHttpUri(testURL) || isHttpsUri(testURL)) && parseURL(testURL) !== null; +} + +function isValidURI(testURL) { + return Boolean(isUri(testURL)); +} + +function parseURL(inputURL) { + if (!inputURL) { + return null; + } + if (inputURL instanceof URL) { + return inputURL; + } + try { + return new URL(inputURL); + } catch (e) { + return null; + } +} + +function getHost(inputURL) { + const parsedURL = parseURL(inputURL); + if (parsedURL) { + return parsedURL.origin; + } + throw new Error(`Couldn't parse url: ${inputURL}`); +} + +// isInternalURL determines if the target url is internal to the application. +// - currentURL is the current url inside the webview +// - basename is the global export from the Mattermost application defining the subpath, if any +function isInternalURL(targetURL, currentURL, basename = '/') { + if (targetURL.host !== currentURL.host) { + return false; + } + + if (!(targetURL.pathname || '/').startsWith(basename)) { + return false; + } + + return true; +} + +function getServerInfo(serverUrl) { + const parsedServer = parseURL(serverUrl); + if (!parsedServer) { + return null; + } + + // does the server have a subpath? + const pn = parsedServer.pathname.toLowerCase(); + const subpath = pn.endsWith('/') ? pn.toLowerCase() : `${pn}/`; + return {origin: parsedServer.origin, subpath, url: parsedServer}; +} + +function getManagedResources() { + if (!buildConfig) { + return []; + } + + return buildConfig.managedResources || []; +} + +function isAdminUrl(serverUrl, inputUrl) { + const parsedURL = parseURL(inputUrl); + const server = getServerInfo(serverUrl); + if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) { + return null; + } + return (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}/admin_console/`) || + parsedURL.pathname.toLowerCase().startsWith('/admin_console/')); +} + +function isTeamUrl(serverUrl, inputUrl, withApi) { + const parsedURL = parseURL(inputUrl); + const server = getServerInfo(serverUrl); + if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) { + return null; + } + + // pre process nonTeamUrlPaths + let nonTeamUrlPaths = [ + 'plugins', + 'signup', + 'login', + 'admin', + 'channel', + 'post', + 'oauth', + 'admin_console', + ]; + const managedResources = getManagedResources(); + nonTeamUrlPaths = nonTeamUrlPaths.concat(managedResources); + + if (withApi) { + nonTeamUrlPaths.push('api'); + } + return !(nonTeamUrlPaths.some((testPath) => ( + parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${testPath}/`) || + parsedURL.pathname.toLowerCase().startsWith(`/${testPath}/`)))); +} + +function isPluginUrl(serverUrl, inputURL) { + const server = getServerInfo(serverUrl); + const parsedURL = parseURL(inputURL); + if (!parsedURL || !server) { + return false; + } + return ( + equalUrlsIgnoringSubpath(server, parsedURL) && + (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}plugins/`) || + parsedURL.pathname.toLowerCase().startsWith('/plugins/'))); +} + +function isManagedResource(serverUrl, inputURL) { + const server = getServerInfo(serverUrl); + const parsedURL = parseURL(inputURL); + if (!parsedURL || !server) { + return false; + } + + const managedResources = getManagedResources(); + + return ( + equalUrlsIgnoringSubpath(server, parsedURL) && managedResources && managedResources.length && + managedResources.some((managedResource) => (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${managedResource}/`) || parsedURL.pathname.toLowerCase().startsWith(`/${managedResource}/`)))); +} + +function getServer(inputURL, teams, ignoreScheme = false) { + const parsedURL = parseURL(inputURL); + if (!parsedURL) { + return null; + } + let parsedServerUrl; + let secondOption = null; + for (let i = 0; i < teams.length; i++) { + parsedServerUrl = parseURL(teams[i].url); + + // check server and subpath matches (without subpath pathname is \ so it always matches) + if (equalUrlsWithSubpath(parsedServerUrl, parsedURL, ignoreScheme)) { + return {name: teams[i].name, url: parsedServerUrl, index: i}; + } + if (equalUrlsIgnoringSubpath(parsedServerUrl, parsedURL, ignoreScheme)) { + // in case the user added something on the path that doesn't really belong to the server + // there might be more than one that matches, but we can't differentiate, so last one + // is as good as any other in case there is no better match (e.g.: two subpath servers with the same origin) + // e.g.: https://community.mattermost.com/core + secondOption = {name: teams[i].name, url: parsedServerUrl, index: i}; + } + } + return secondOption; +} + +// next two functions are defined to clarify intent +function equalUrlsWithSubpath(url1, url2, ignoreScheme) { + if (ignoreScheme) { + return url1.host === url2.host && url2.pathname.toLowerCase().startsWith(url1.pathname.toLowerCase()); + } + return url1.origin === url2.origin && url2.pathname.toLowerCase().startsWith(url1.pathname.toLowerCase()); +} + +function equalUrlsIgnoringSubpath(url1, url2, ignoreScheme) { + if (ignoreScheme) { + return url1.host.toLowerCase() === url2.host.toLowerCase(); + } + return url1.origin.toLowerCase() === url2.origin.toLowerCase(); +} + +function isTrustedURL(url, teams) { + const parsedURL = parseURL(url); + if (!parsedURL) { + return false; + } + return getServer(parsedURL, teams) !== null; +} + +function isCustomLoginURL(url, server, teams) { + const subpath = (server === null || typeof server === 'undefined') ? '' : server.url.pathname; + const parsedURL = parseURL(url); + if (!parsedURL) { + return false; + } + if (!isTrustedURL(parsedURL, teams)) { + return false; + } + const urlPath = parsedURL.pathname; + if ((subpath !== '' || subpath !== '/') && urlPath.startsWith(subpath)) { + const replacement = subpath.endsWith('/') ? '/' : ''; + const replacedPath = urlPath.replace(subpath, replacement); + for (const regexPath of customLoginRegexPaths) { + if (replacedPath.match(regexPath)) { + return true; + } + } + } + + // if there is no subpath, or we are adding the team and got redirected to the real server it'll be caught here + for (const regexPath of customLoginRegexPaths) { + if (urlPath.match(regexPath)) { + return true; + } + } + return false; +} + +export default { + getDomain, + isValidURL, + isValidURI, + isInternalURL, + parseURL, + getServer, + getServerInfo, + isAdminUrl, + isTeamUrl, + isPluginUrl, + isManagedResource, + getHost, + isTrustedURL, + isCustomLoginURL, +}; diff --git a/src/common/utils/util.js b/src/common/utils/util.js new file mode 100644 index 00000000..9f82be65 --- /dev/null +++ b/src/common/utils/util.js @@ -0,0 +1,51 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import electron, {BrowserWindow} from 'electron'; + +import {DEVELOPMENT, PRODUCTION} from './constants'; + +function getDisplayBoundaries() { + const {screen} = electron; + + const displays = screen.getAllDisplays(); + + return displays.map((display) => { + return { + maxX: display.workArea.x + display.workArea.width, + maxY: display.workArea.y + display.workArea.height, + minX: display.workArea.x, + minY: display.workArea.y, + maxWidth: display.workArea.width, + maxHeight: display.workArea.height, + }; + }); +} + +function runMode() { + return process.env.NODE_ENV === PRODUCTION ? PRODUCTION : DEVELOPMENT; +} + +// workaround until electron 12 hits, since fromWebContents return a null value if using a webcontent from browserview +function browserWindowFromWebContents(content) { + let window; + if (content.type === 'browserview') { + for (const win of BrowserWindow.getAllWindows()) { + for (const view of win.getBrowserViews()) { + if (view.webContents.id === content.id) { + window = win; + } + } + } + } else { + window = BrowserWindow.fromWebContents(content); + } + return window; +} + +export default { + getDisplayBoundaries, + runMode, + browserWindowFromWebContents, +}; diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 42d17a40..00000000 --- a/src/main.js +++ /dev/null @@ -1,1226 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import path from 'path'; -import fs from 'fs'; - -import electron, {nativeTheme} from 'electron'; -import isDev from 'electron-is-dev'; -import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-installer'; -import log from 'electron-log'; -import 'airbnb-js-shims/target/es2015'; - -import {protocols} from '../electron-builder.json'; - -import AutoLauncher from './main/AutoLauncher'; -import CriticalErrorHandler from './main/CriticalErrorHandler'; -import upgradeAutoLaunch from './main/autoLaunch'; - -import RegistryConfig from './common/config/RegistryConfig'; -import Config from './common/config'; -import CertificateStore from './main/certificateStore'; -import TrustedOriginsStore from './main/trustedOrigins'; -import createMainWindow from './main/mainWindow'; -import appMenu from './main/menus/app'; -import trayMenu from './main/menus/tray'; -import downloadURL from './main/downloadURL'; -import allowProtocolDialog from './main/allowProtocolDialog'; -import AppStateManager from './main/AppStateManager'; -import initCookieManager from './main/cookieManager'; -import SpellChecker from './main/SpellChecker'; -import UserActivityMonitor from './main/UserActivityMonitor'; -import Utils from './utils/util'; -import urlUtils from './utils/url'; -import parseArgs from './main/ParseArgs'; -import { - REQUEST_PERMISSION_CHANNEL, - GRANT_PERMISSION_CHANNEL, - DENY_PERMISSION_CHANNEL, - BASIC_AUTH_PERMISSION -} from './common/permissions'; - -// pull out required electron components like this -// as not all components can be referenced before the app is ready -const { - app, - Menu, - Tray, - ipcMain, - nativeImage, - dialog, - systemPreferences, - session, - BrowserWindow, -} = electron; -const criticalErrorHandler = new CriticalErrorHandler(); -const assetsDir = path.resolve(app.getAppPath(), 'assets'); -const loginCallbackMap = new Map(); -const certificateRequests = new Map(); -const userActivityMonitor = new UserActivityMonitor(); -const certificateErrorCallbacks = new Map(); - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow = null; -let popupWindow = null; -let certificateStore = null; -let trustedOriginsStore = null; -let spellChecker = null; -let deeplinkingUrl = null; -let scheme = null; -let appState = null; -let registryConfig = null; -let config = null; -let trayIcon = null; -let trayImages = null; -let altLastPressed = false; - -// supported custom login paths (oath, saml) -const customLoginRegexPaths = [ - /^\/oauth\/authorize$/i, - /^\/oauth\/deauthorize$/i, - /^\/oauth\/access_token$/i, - /^\/oauth\/[A-Za-z0-9]+\/complete$/i, - /^\/oauth\/[A-Za-z0-9]+\/login$/i, - /^\/oauth\/[A-Za-z0-9]+\/signup$/i, - /^\/api\/v3\/oauth\/[A-Za-z0-9]+\/complete$/i, - /^\/signup\/[A-Za-z0-9]+\/complete$/i, - /^\/login\/[A-Za-z0-9]+\/complete$/i, - /^\/login\/sso\/saml$/i, -]; - -// tracking in progress custom logins -const customLogins = {}; - -const nixUA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari/537.36'; - -const popupUserAgent = { - darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari/537.36', - win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari/537.36', - aix: nixUA, - freebsd: nixUA, - linux: nixUA, - openbsd: nixUA, - sunos: nixUA, -}; - -/** - * Main entry point for the application, ensures that everything initializes in the proper order - */ -async function initialize() { - process.on('uncaughtException', criticalErrorHandler.processUncaughtExceptionHandler.bind(criticalErrorHandler)); - global.willAppQuit = false; - - // initialization that can run before the app is ready - initializeArgs(); - initializeConfig(); - initializeAppEventListeners(); - initializeBeforeAppReady(); - - // wait for registry config data to load and app ready event - await Promise.all([ - registryConfig.init(), - app.whenReady(), - ]); - - // no need to continue initializing if app is quitting - if (global.willAppQuit) { - return; - } - - // initialization that should run once the app is ready - initializeInterCommunicationEventListeners(); - initializeAfterAppReady(); - initializeMainWindowListeners(); -} - -// attempt to initialize the application -try { - initialize(); -} catch (error) { - throw new Error(`App initialization failed: ${error.toString()}`); -} - -// -// initialization sub functions -// - -function initializeArgs() { - global.args = parseArgs(process.argv.slice(1)); - - // output the application version via cli when requested (-v or --version) - if (global.args.version) { - process.stdout.write(`v.${app.getVersion()}\n`); - process.exit(0); // eslint-disable-line no-process-exit - } - - global.isDev = isDev && !global.args.disableDevMode; // this doesn't seem to be right and isn't used as the single source of truth - - if (global.args.dataDir) { - app.setPath('userData', path.resolve(global.args.dataDir)); - } -} - -function initializeConfig() { - registryConfig = new RegistryConfig(); - config = new Config(app.getPath('userData') + '/config.json'); - config.on('update', handleConfigUpdate); - config.on('synchronize', handleConfigSynchronize); -} - -function initializeAppEventListeners() { - app.on('second-instance', handleAppSecondInstance); - app.on('window-all-closed', handleAppWindowAllClosed); - app.on('browser-window-created', handleAppBrowserWindowCreated); - app.on('activate', handleAppActivate); - app.on('before-quit', handleAppBeforeQuit); - app.on('certificate-error', handleAppCertificateError); - app.on('select-client-certificate', handleSelectCertificate); - app.on('gpu-process-crashed', handleAppGPUProcessCrashed); - app.on('login', handleAppLogin); - app.on('will-finish-launching', handleAppWillFinishLaunching); - app.on('web-contents-created', handleAppWebContentsCreated); -} - -function initializeBeforeAppReady() { - certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json')); - trustedOriginsStore = new TrustedOriginsStore(path.resolve(app.getPath('userData'), 'trustedOrigins.json')); - trustedOriginsStore.load(); - - // prevent using a different working directory, which happens on windows running after installation. - const expectedPath = path.dirname(process.execPath); - if (process.cwd() !== expectedPath && !isDev) { - log.warn(`Current working directory is ${process.cwd()}, changing into ${expectedPath}`); - process.chdir(expectedPath); - } - - // can only call this before the app is ready - if (config.enableHardwareAcceleration === false) { - app.disableHardwareAcceleration(); - } - - trayImages = getTrayImages(); - - // If there is already an instance, quit this one - const gotTheLock = app.requestSingleInstanceLock(); - if (!gotTheLock) { - app.exit(); - global.willAppQuit = true; - } - - if (!config.spellCheckerLocale) { - config.set('spellCheckerLocale', SpellChecker.getSpellCheckerLocale(app.getLocale())); - } - - allowProtocolDialog.init(mainWindow); - - if (isDev) { - console.log('In development mode, deeplinking is disabled'); - } else if (protocols && protocols[0] && protocols[0].schemes && protocols[0].schemes[0]) { - scheme = protocols[0].schemes[0]; - app.setAsDefaultProtocolClient(scheme); - } -} - -function initializeInterCommunicationEventListeners() { - ipcMain.on('reload-config', handleReloadConfig); - ipcMain.on('login-credentials', handleLoginCredentialsEvent); - ipcMain.on('login-cancel', handleCancelLoginEvent); - ipcMain.on('download-url', handleDownloadURLEvent); - ipcMain.on('notified', handleNotifiedEvent); - ipcMain.on('update-title', handleUpdateTitleEvent); - ipcMain.on('update-menu', handleUpdateMenuEvent); - ipcMain.on('update-dict', handleUpdateDictionaryEvent); - ipcMain.on('checkspell', handleCheckSpellingEvent); - ipcMain.on('get-spelling-suggestions', handleGetSpellingSuggestionsEvent); - ipcMain.on('get-spellchecker-locale', handleGetSpellcheckerLocaleEvent); - ipcMain.on('reply-on-spellchecker-is-ready', handleReplyOnSpellcheckerIsReadyEvent); - ipcMain.on('selected-client-certificate', handleSelectedCertificate); - ipcMain.on(GRANT_PERMISSION_CHANNEL, handlePermissionGranted); - ipcMain.on(DENY_PERMISSION_CHANNEL, handlePermissionDenied); - - if (shouldShowTrayIcon()) { - ipcMain.on('update-unread', handleUpdateUnreadEvent); - } - if (process.platform !== 'darwin') { - ipcMain.on('open-app-menu', handleOpenAppMenu); - } -} - -function initializeMainWindowListeners() { - mainWindow.on('closed', handleMainWindowClosed); - mainWindow.on('unresponsive', criticalErrorHandler.windowUnresponsiveHandler.bind(criticalErrorHandler)); - mainWindow.webContents.on('crashed', handleMainWindowWebContentsCrashed); -} - -// -// config event handlers -// - -function handleConfigUpdate(configData) { - if (process.platform === 'win32' || process.platform === 'linux') { - const appLauncher = new AutoLauncher(); - const autoStartTask = config.autostart ? appLauncher.enable() : appLauncher.disable(); - autoStartTask.then(() => { - console.log('config.autostart has been configured:', config.autostart); - }).catch((err) => { - console.log('error:', err); - }); - } - - ipcMain.emit('update-menu', true, configData); -} - -function handleConfigSynchronize() { - if (mainWindow) { - mainWindow.webContents.send('reload-config'); - } -} - -function handleReloadConfig() { - config.reload(); -} - -// -// app event handlers -// - -// activate first app instance, subsequent instances will quit themselves -function handleAppSecondInstance(event, argv) { - // Protocol handler for win32 - // argv: An array of the second instance’s (command line / deep linked) arguments - if (process.platform === 'win32') { - deeplinkingUrl = getDeeplinkingURL(argv); - if (deeplinkingUrl) { - mainWindow.webContents.send('protocol-deeplink', deeplinkingUrl); - } - } - - // Someone tried to run a second instance, we should focus our window. - if (mainWindow) { - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } else { - mainWindow.show(); - } - } -} - -function handleAppWindowAllClosed() { - // On OS X it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit(); - } -} - -function handleAppBrowserWindowCreated(error, newWindow) { - // Screen cannot be required before app is ready - resizeScreen(electron.screen, newWindow); -} - -function handleAppActivate() { - mainWindow.show(); -} - -function handleAppBeforeQuit() { - // Make sure tray icon gets removed if the user exits via CTRL-Q - if (trayIcon && process.platform === 'win32') { - trayIcon.destroy(); - } - global.willAppQuit = true; -} - -function handleSelectCertificate(event, webContents, url, list, callback) { - if (list.length > 1) { - event.preventDefault(); // prevent the app from getting the first certificate available - // store callback so it can be called with selected certificate - certificateRequests.set(url, callback); - - // open modal for selecting certificate - mainWindow.webContents.send('select-user-certificate', url, list); - } else { - log.info(`There were ${list.length} candidate certificates. Skipping certificate selection`); - } -} - -function handleSelectedCertificate(event, server, cert) { - const callback = certificateRequests.get(server); - if (!callback) { - log.error(`there was no callback associated with: ${server}`); - return; - } - if (typeof cert === 'undefined') { - log.info('user canceled certificate selection'); - } else { - try { - callback(cert); - } catch (e) { - log.error(`There was a problem using the selected certificate: ${e}`); - } - } -} - -function handleAppCertificateError(event, webContents, url, error, certificate, callback) { - const parsedURL = urlUtils.parseURL(url); - if (!parsedURL) { - return; - } - const origin = parsedURL.origin; - if (certificateStore.isTrusted(origin, certificate)) { - event.preventDefault(); - callback(true); - } else { - // update the callback - const errorID = `${origin}:${error}`; - - // if we are already showing that error, don't add more dialogs - if (certificateErrorCallbacks.has(errorID)) { - log.warn(`Ignoring already shown dialog for ${errorID}`); - certificateErrorCallbacks.set(errorID, callback); - return; - } - const extraDetail = certificateStore.isExisting(origin) ? 'Certificate is different from previous one.\n\n' : ''; - const detail = `${extraDetail}origin: ${origin}\nError: ${error}`; - - certificateErrorCallbacks.set(errorID, callback); - dialog.showMessageBox(mainWindow, { - title: 'Certificate Error', - message: 'There is a configuration issue with this Mattermost server, or someone is trying to intercept your connection. You also may need to sign into the Wi-Fi you are connected to using your web browser.', - type: 'error', - detail, - buttons: ['More Details', 'Cancel Connection'], - cancelId: 1, - }).then( - ({response}) => { - if (response === 0) { - return dialog.showMessageBox(mainWindow, { - title: 'Certificate Not Trusted', - message: `Certificate from "${certificate.issuerName}" is not trusted.`, - detail: extraDetail, - type: 'error', - buttons: ['Trust Insecure Certificate', 'Cancel Connection'], - cancelId: 1, - }); - } - return {response}; - }).then( - ({response: responseTwo}) => { - if (responseTwo === 0) { - certificateStore.add(origin, certificate); - certificateStore.save(); - certificateErrorCallbacks.get(errorID)(true); - certificateErrorCallbacks.delete(errorID); - webContents.loadURL(url); - } else { - certificateErrorCallbacks.get(errorID)(false); - certificateErrorCallbacks.delete(errorID); - } - }).catch( - (dialogError) => { - log.error(`There was an error with the Certificate Error dialog: ${dialogError}`); - certificateErrorCallbacks.delete(errorID); - }); - } -} - -function handleAppGPUProcessCrashed(event, killed) { - console.log(`The GPU process has crashed (killed = ${killed})`); -} - -function handleAppLogin(event, webContents, request, authInfo, callback) { - event.preventDefault(); - const parsedURL = urlUtils.parseURL(request.url); - const server = urlUtils.getServer(parsedURL, config.teams); - - loginCallbackMap.set(request.url, typeof callback === 'undefined' ? null : callback); // if callback is undefined set it to null instead so we know we have set it up with no value - if (isTrustedURL(request.url) || isCustomLoginURL(parsedURL, server) || trustedOriginsStore.checkPermission(request.url, BASIC_AUTH_PERMISSION)) { - mainWindow.webContents.send('login-request', request, authInfo); - } else { - mainWindow.webContents.send(REQUEST_PERMISSION_CHANNEL, request, authInfo, BASIC_AUTH_PERMISSION); - } -} - -function handlePermissionGranted(event, url, permission) { - trustedOriginsStore.addPermission(url, permission); - trustedOriginsStore.save(); -} - -function handlePermissionDenied(event, url, permission, reason) { - log.warn(`Permission request denied: ${reason}`); -} - -function handleAppWillFinishLaunching() { - // Protocol handler for osx - app.on('open-url', (event, url) => { - event.preventDefault(); - deeplinkingUrl = getDeeplinkingURL([url]); - if (app.isReady()) { - function openDeepLink() { - try { - if (deeplinkingUrl) { - mainWindow.webContents.send('protocol-deeplink', deeplinkingUrl); - mainWindow.show(); - } - } catch (err) { - setTimeout(openDeepLink, 1000); - } - } - - openDeepLink(); - } - }); -} - -function handleAppWebContentsCreated(dc, contents) { - // initialize custom login tracking - customLogins[contents.id] = { - inProgress: false, - }; - - contents.on('will-attach-webview', (event, webPreferences) => { - webPreferences.nodeIntegration = false; - webPreferences.contextIsolation = true; - }); - - contents.on('will-navigate', (event, url) => { - const contentID = event.sender.id; - const parsedURL = urlUtils.parseURL(url); - const server = urlUtils.getServer(parsedURL, config.teams); - - if ((server !== null && (urlUtils.isTeamUrl(server.url, parsedURL) || urlUtils.isAdminUrl(server.url, parsedURL))) || - isTrustedPopupWindow(event.sender)) { - return; - } - - if (isCustomLoginURL(parsedURL, server)) { - return; - } - if (parsedURL.protocol === 'mailto:') { - return; - } - if (customLogins[contentID].inProgress) { - return; - } - - log.info(`Prevented desktop from navigating to: ${url}`); - event.preventDefault(); - }); - - // handle custom login requests (oath, saml): - // 1. are we navigating to a supported local custom login path from the `/login` page? - // - indicate custom login is in progress - // 2. are we finished with the custom login process? - // - indicate custom login is NOT in progress - contents.on('did-start-navigation', (event, url) => { - const contentID = event.sender.id; - const parsedURL = urlUtils.parseURL(url); - const server = urlUtils.getServer(parsedURL, config.teams); - - if (!isTrustedURL(parsedURL)) { - return; - } - - if (isCustomLoginURL(parsedURL, server)) { - customLogins[contentID].inProgress = true; - } else if (customLogins[contentID].inProgress) { - customLogins[contentID].inProgress = false; - } - }); - - contents.on('new-window', (event, url) => { - event.preventDefault(); - - const parsedURL = urlUtils.parseURL(url); - const server = urlUtils.getServer(parsedURL, config.teams); - - if (!server) { - log.info(`Untrusted popup window blocked: ${url}`); - return; - } - if (urlUtils.isTeamUrl(server.url, parsedURL, true)) { - log.info(`${url} is a known team, preventing to open a new window`); - return; - } - if (urlUtils.isAdminUrl(server.url, parsedURL)) { - log.info(`${url} is an admin console page, preventing to open a new window`); - return; - } - if (popupWindow && !popupWindow.closed && popupWindow.getURL() === url) { - log.info(`Popup window already open at provided url: ${url}`); - return; - } - if (urlUtils.isPluginUrl(server.url, parsedURL) || urlUtils.isManagedResource(server.url, parsedURL)) { - if (!popupWindow || popupWindow.closed) { - popupWindow = new BrowserWindow({ - backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do - parent: mainWindow, - show: false, - center: true, - webPreferences: { - nodeIntegration: false, - contextIsolation: true, - }, - }); - popupWindow.once('ready-to-show', () => { - popupWindow.show(); - }); - popupWindow.once('closed', () => { - popupWindow = null; - }); - } - - if (urlUtils.isManagedResource(server.url, parsedURL)) { - popupWindow.loadURL(url); - } else { - // currently changing the userAgent for popup windows to allow plugins to go through google's oAuth - // should be removed once a proper oAuth2 implementation is setup. - popupWindow.loadURL(url, { - userAgent: popupUserAgent[process.platform], - }); - } - } - }); - - // implemented to temporarily help solve for https://community-daily.mattermost.com/core/pl/b95bi44r4bbnueqzjjxsi46qiw - contents.on('before-input-event', (event, input) => { - if (input.key === 'Alt' && input.type === 'keyUp' && altLastPressed) { - altLastPressed = false; - mainWindow.webContents.send('focus-three-dot-menu'); - return; - } - - // Hack to detect keyPress so that alt+ combinations don't default back to the 3-dot menu - if (input.key === 'Alt' && input.type === 'keyDown') { - altLastPressed = true; - } else { - altLastPressed = false; - } - - if (!input.shift && !input.control && !input.alt && !input.meta) { - // hacky fix for https://mattermost.atlassian.net/browse/MM-19226 - if ((input.key === 'Escape' || input.key === 'f') && input.type === 'keyDown') { - // only do this when in fullscreen on a mac - if (mainWindow.isFullScreen() && process.platform === 'darwin') { - mainWindow.webContents.send('exit-fullscreen'); - } - } - return; - } - - if ((process.platform === 'darwin' && !input.meta) || (process.platform !== 'darwin' && !input.control)) { - return; - } - - // handle certain keyboard shortcuts manually - switch (input.key) { // eslint-disable-line padded-blocks - - // Manually handle zoom-in/out/reset keyboard shortcuts - // - temporary fix for https://mattermost.atlassian.net/browse/MM-19031 and https://mattermost.atlassian.net/browse/MM-19032 - case '-': - mainWindow.webContents.send('zoom-out'); - break; - case '=': - mainWindow.webContents.send('zoom-in'); - break; - case '0': - mainWindow.webContents.send('zoom-reset'); - break; - - // Manually handle undo/redo keyboard shortcuts - // - temporary fix for https://mattermost.atlassian.net/browse/MM-19198 - case 'z': - if (input.shift) { - mainWindow.webContents.send('redo'); - } else { - mainWindow.webContents.send('undo'); - } - break; - - // Manually handle copy/cut/paste keyboard shortcuts - case 'c': - mainWindow.webContents.send('copy'); - break; - case 'x': - mainWindow.webContents.send('cut'); - break; - case 'v': - if (input.shift) { - mainWindow.webContents.send('paste-and-match'); - } else { - mainWindow.webContents.send('paste'); - } - break; - default: - // allows the input event to proceed if not handled by a case above - return; - } - event.preventDefault(); - }); -} - -function initializeAfterAppReady() { - app.setAppUserModelId('Mattermost.Desktop'); // Use explicit AppUserModelID - - const appStateJson = path.join(app.getPath('userData'), 'app-state.json'); - appState = new AppStateManager(appStateJson); - if (wasUpdated(appState.lastAppVersion)) { - clearAppCache(); - } - appState.lastAppVersion = app.getVersion(); - - if (!global.isDev) { - upgradeAutoLaunch(); - } - - if (global.isDev) { - installExtension(REACT_DEVELOPER_TOOLS). - then((name) => console.log(`Added Extension: ${name}`)). - catch((err) => console.log('An error occurred: ', err)); - } - - // Workaround for MM-22193 - // From this post: https://github.com/electron/electron/issues/19468#issuecomment-549593139 - // Electron 6 has a bug that affects users on Windows 10 using dark mode, causing the app to hang - // This workaround deletes a file that stops that from happening - if (process.platform === 'win32') { - const appUserDataPath = app.getPath('userData'); - const devToolsExtensionsPath = path.join(appUserDataPath, 'DevTools Extensions'); - try { - fs.unlinkSync(devToolsExtensionsPath); - } catch (_) { - // don't complain if the file doesn't exist - } - } - - // Protocol handler for win32 - if (process.platform === 'win32') { - const args = process.argv.slice(1); - if (Array.isArray(args) && args.length > 0) { - deeplinkingUrl = getDeeplinkingURL(args); - } - } - - initCookieManager(session.defaultSession); - - mainWindow = createMainWindow(config.data, { - trayIconShown: process.platform === 'win32' || config.showTrayIcon, - linuxAppIcon: path.join(assetsDir, 'appicon.png'), - deeplinkingUrl, - }); - - criticalErrorHandler.setMainWindow(mainWindow); - - config.setRegistryConfigData(registryConfig.data); - mainWindow.registryConfigData = registryConfig.data; - - // listen for status updates and pass on to renderer - userActivityMonitor.on('status', (status) => { - mainWindow.webContents.send('user-activity-update', status); - }); - - // start monitoring user activity (needs to be started after the app is ready) - userActivityMonitor.startMonitoring(); - - if (shouldShowTrayIcon()) { - // set up tray icon - trayIcon = new Tray(trayImages.normal); - if (process.platform === 'darwin') { - trayIcon.setPressedImage(trayImages.clicked.normal); - systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => { - switchMenuIconImages(trayImages, nativeTheme.shouldUseDarkColors); - trayIcon.setImage(trayImages.normal); - }); - } - - trayIcon.setToolTip(app.name); - trayIcon.on('click', () => { - if (!mainWindow.isVisible() || mainWindow.isMinimized()) { - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } else { - mainWindow.show(); - } - mainWindow.focus(); - if (process.platform === 'darwin') { - app.dock.show(); - } - } else { - mainWindow.focus(); - } - }); - - trayIcon.on('right-click', () => { - trayIcon.popUpContextMenu(); - }); - trayIcon.on('balloon-click', () => { - if (process.platform === 'win32' || process.platform === 'darwin') { - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } else { - mainWindow.show(); - } - } - - if (process.platform === 'darwin') { - app.dock.show(); - } - - mainWindow.focus(); - }); - } - - session.defaultSession.on('will-download', (event, item, webContents) => { - const filename = item.getFilename(); - const fileElements = filename.split('.'); - const filters = []; - if (fileElements.length > 1) { - filters.push({ - name: `${fileElements[fileElements.length - 1]} files`, - extensions: [fileElements[fileElements.length - 1]], - }); - } - - // add default filter - filters.push({ - name: 'All files', - extensions: ['*'], - }); - item.setSaveDialogOptions({ - title: filename, - defaultPath: path.resolve(config.combinedData.downloadLocation, filename), - filters, - }); - - item.on('done', (doneEvent, state) => { - if (state === 'completed') { - mainWindow.webContents.send('download-complete', { - fileName: filename, - path: item.savePath, - serverInfo: urlUtils.getServer(webContents.getURL(), config.teams), - }); - } - }); - }); - - // Code for preventing user-agent checking. See: https://mattermost.atlassian.net/browse/MM-31626 - const REMOVE_CHROME_FROM = ['https://gitlab.com/*']; - const nonChromeUA = session.defaultSession.getUserAgent().replace(/ Chrome\/[\d.]+/g, ''); - session.defaultSession.webRequest.onBeforeSendHeaders({urls: REMOVE_CHROME_FROM}, (details, callback) => { - console.warn(`Cloud-flare url detected for ${details.url}, request will use a different User Agent`); - const temporaryHeaders = details.requestHeaders; - temporaryHeaders['User-Agent'] = nonChromeUA; - callback({cancel: false, requestHeaders: temporaryHeaders}); - }); - - ipcMain.emit('update-menu', true, config.data); - - ipcMain.emit('update-dict'); - - // supported permission types - const supportedPermissionTypes = [ - 'media', - 'geolocation', - 'notifications', - 'fullscreen', - 'openExternal', - ]; - - // handle permission requests - // - approve if a supported permission type and the request comes from the renderer or one of the defined servers - session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { - // is the requested permission type supported? - if (!supportedPermissionTypes.includes(permission)) { - callback(false); - return; - } - - // is the request coming from the renderer? - if (webContents.id === mainWindow.webContents.id) { - callback(true); - return; - } - - const requestingURL = webContents.getURL(); - - // is the requesting url trusted? - callback(isTrustedURL(requestingURL)); - }); -} - -// -// ipc communication event handlers -// - -function handleLoginCredentialsEvent(event, request, user, password) { - const callback = loginCallbackMap.get(request.url); - if (typeof callback === 'undefined') { - log.error(`Failed to retrieve login callback for ${request.url}`); - return; - } - if (callback != null) { - callback(user, password); - } - loginCallbackMap.delete(request.url); -} - -function handleCancelLoginEvent(event, request) { - log.info(`Cancelling request for ${request ? request.url : 'unknown'}`); - handleLoginCredentialsEvent(event, request); // we use undefined to cancel the request -} - -function handleDownloadURLEvent(event, url) { - downloadURL(mainWindow, url, (err) => { - if (err) { - dialog.showMessageBox(mainWindow, { - type: 'error', - message: err.toString(), - }); - log.error(err); - } - }); -} - -function handleNotifiedEvent() { - if (process.platform === 'win32' || process.platform === 'linux') { - if (config.notifications.flashWindow === 2) { - mainWindow.flashFrame(true); - } - } - - if (process.platform === 'darwin' && config.notifications.bounceIcon) { - app.dock.bounce(config.notifications.bounceIconType); - } -} - -function handleUpdateTitleEvent(event, arg) { - mainWindow.setTitle(arg.title); -} - -function handleUpdateUnreadEvent(event, arg) { - if (process.platform === 'win32') { - const overlay = arg.overlayDataURL ? nativeImage.createFromDataURL(arg.overlayDataURL) : null; - if (mainWindow) { - mainWindow.setOverlayIcon(overlay, arg.description); - } - } - - if (trayIcon && !trayIcon.isDestroyed()) { - if (arg.sessionExpired) { - // reuse the mention icon when the session is expired - trayIcon.setImage(trayImages.mention); - if (process.platform === 'darwin') { - trayIcon.setPressedImage(trayImages.clicked.mention); - } - trayIcon.setToolTip('Session Expired: Please sign in to continue receiving notifications.'); - } else if (arg.mentionCount > 0) { - trayIcon.setImage(trayImages.mention); - if (process.platform === 'darwin') { - trayIcon.setPressedImage(trayImages.clicked.mention); - } - trayIcon.setToolTip(arg.mentionCount + ' unread mentions'); - } else if (arg.unreadCount > 0) { - trayIcon.setImage(trayImages.unread); - if (process.platform === 'darwin') { - trayIcon.setPressedImage(trayImages.clicked.unread); - } - trayIcon.setToolTip(arg.unreadCount + ' unread channels'); - } else { - trayIcon.setImage(trayImages.normal); - if (process.platform === 'darwin') { - trayIcon.setPressedImage(trayImages.clicked.normal); - } - trayIcon.setToolTip(app.name); - } - } -} - -function handleOpenAppMenu() { - Menu.getApplicationMenu().popup({ - x: 18, - y: 18, - }); -} - -function handleCloseAppMenu(event) { - mainWindow.webContents.send('focus-on-webview', event); -} - -function handleUpdateMenuEvent(event, configData) { - const aMenu = appMenu.createMenu(mainWindow, configData, global.isDev); - Menu.setApplicationMenu(aMenu); - aMenu.addListener('menu-will-close', handleCloseAppMenu); - - // set up context menu for tray icon - if (shouldShowTrayIcon()) { - const tMenu = trayMenu.createMenu(mainWindow, configData, global.isDev); - if (process.platform === 'darwin' || process.platform === 'linux') { - // store the information, if the tray was initialized, for checking in the settings, if the application - // was restarted after setting "Show icon on menu bar" - if (trayIcon) { - trayIcon.setContextMenu(tMenu); - mainWindow.trayWasVisible = true; - } else { - mainWindow.trayWasVisible = false; - } - } else if (trayIcon) { - trayIcon.setContextMenu(tMenu); - } - } -} - -// localeSelected might be null, if that's the case, use config's locale -function handleUpdateDictionaryEvent(_, localeSelected) { - if (config.useSpellChecker) { - const locale = localeSelected || config.spellCheckerLocale; - try { - spellChecker = new SpellChecker( - locale, - path.resolve(app.getAppPath(), 'node_modules/simple-spellchecker/dict'), - (err) => { - if (err) { - log.error(err); - } - }); - } catch (e) { - log.error('couldn\'t load a spellchecker for locale'); - } - } -} - -function handleCheckSpellingEvent(event, word) { - let res = null; - if (config.useSpellChecker && spellChecker.isReady() && word !== null) { - res = spellChecker.spellCheck(word); - } - event.returnValue = res; -} - -function handleGetSpellingSuggestionsEvent(event, word) { - if (config.useSpellChecker && spellChecker.isReady() && word !== null) { - event.returnValue = spellChecker.getSuggestions(word, 10); - } else { - event.returnValue = []; - } -} - -function handleGetSpellcheckerLocaleEvent(event) { - event.returnValue = config.spellCheckerLocale; -} - -function handleReplyOnSpellcheckerIsReadyEvent(event) { - if (!spellChecker) { - return; - } - - if (spellChecker.isReady()) { - event.sender.send('spellchecker-is-ready'); - return; - } - spellChecker.once('ready', () => { - event.sender.send('spellchecker-is-ready'); - }); -} - -// -// mainWindow event handlers -// - -function handleMainWindowClosed() { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null; -} - -function handleMainWindowWebContentsCrashed() { - throw new Error('webContents \'crashed\' event has been emitted'); -} - -// -// helper functions -// - -function isTrustedURL(url) { - const parsedURL = urlUtils.parseURL(url); - if (!parsedURL) { - return false; - } - return urlUtils.getServer(parsedURL, config.teams) !== null; -} - -function isTrustedPopupWindow(webContents) { - if (!webContents) { - return false; - } - if (!popupWindow) { - return false; - } - return BrowserWindow.fromWebContents(webContents) === popupWindow; -} - -function isCustomLoginURL(url, server) { - const subpath = (server === null || typeof server === 'undefined') ? '' : server.url.pathname; - const parsedURL = urlUtils.parseURL(url); - if (!parsedURL) { - return false; - } - if (!isTrustedURL(parsedURL)) { - return false; - } - const urlPath = parsedURL.pathname; - if ((subpath !== '' || subpath !== '/') && urlPath.startsWith(subpath)) { - const replacement = subpath.endsWith('/') ? '/' : ''; - const replacedPath = urlPath.replace(subpath, replacement); - for (const regexPath of customLoginRegexPaths) { - if (replacedPath.match(regexPath)) { - return true; - } - } - } - - // if there is no subpath, or we are adding the team and got redirected to the real server it'll be caught here - for (const regexPath of customLoginRegexPaths) { - if (urlPath.match(regexPath)) { - return true; - } - } - return false; -} - -function getTrayImages() { - switch (process.platform) { - case 'win32': - return { - normal: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray.ico')), - unread: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_unread.ico')), - mention: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_mention.ico')), - }; - case 'darwin': { - const icons = { - light: { - normal: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIcon.png')), - unread: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIconUnread.png')), - mention: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIconMention.png')), - }, - clicked: { - normal: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIcon.png')), - unread: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIconUnread.png')), - mention: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIconMention.png')), - }, - }; - switchMenuIconImages(icons, nativeTheme.shouldUseDarkColors); - return icons; - } - case 'linux': { - const theme = config.trayIconTheme; - try { - return { - normal: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconTemplate.png')), - unread: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconUnreadTemplate.png')), - mention: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconMentionTemplate.png')), - }; - } catch (e) { - //Fallback for invalid theme setting - return { - normal: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconTemplate.png')), - unread: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconUnreadTemplate.png')), - mention: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconMentionTemplate.png')), - }; - } - } - default: - return {}; - } -} - -function switchMenuIconImages(icons, isDarkMode) { - if (isDarkMode) { - icons.normal = icons.clicked.normal; - icons.unread = icons.clicked.unread; - icons.mention = icons.clicked.mention; - } else { - icons.normal = icons.light.normal; - icons.unread = icons.light.unread; - icons.mention = icons.light.mention; - } -} - -function getDeeplinkingURL(args) { - if (Array.isArray(args) && args.length) { - // deeplink urls should always be the last argument, but may not be the first (i.e. Windows with the app already running) - const url = args[args.length - 1]; - if (url && scheme && url.startsWith(scheme) && urlUtils.isValidURI(url)) { - return url; - } - } - return null; -} - -function shouldShowTrayIcon() { - if (config.showTrayIcon === true || process.platform === 'win32') { - return true; - } - return false; -} - -function wasUpdated(lastAppVersion) { - return lastAppVersion !== app.getVersion(); -} - -function clearAppCache() { - if (mainWindow) { - mainWindow.webContents.session.clearCache().then(mainWindow.reload); - } else { - //Wait for mainWindow - setTimeout(clearAppCache, 100); - } -} - -function isWithinDisplay(state, display) { - const startsWithinDisplay = !(state.x > display.maxX || state.y > display.maxY || state.x < display.minX || state.y < display.minY); - if (!startsWithinDisplay) { - return false; - } - - // is half the screen within the display? - const midX = state.x + (state.width / 2); - const midY = state.y + (state.height / 2); - return !(midX > display.maxX || midY > display.maxY); -} - -function getValidWindowPosition(state) { - // Check if the previous position is out of the viewable area - // (e.g. because the screen has been plugged off) - const boundaries = Utils.getDisplayBoundaries(); - const display = boundaries.find((boundary) => { - return isWithinDisplay(state, boundary); - }); - - if (typeof display === 'undefined') { - return {}; - } - return {x: state.x, y: state.y}; -} - -function resizeScreen(screen, browserWindow) { - function handle() { - const position = browserWindow.getPosition(); - const size = browserWindow.getSize(); - const validPosition = getValidWindowPosition({ - x: position[0], - y: position[1], - width: size[0], - height: size[1], - }); - if (typeof validPosition.x !== 'undefined' || typeof validPosition.y !== 'undefined') { - browserWindow.setPosition(validPosition.x || 0, validPosition.y || 0); - } else { - browserWindow.center(); - } - } - - browserWindow.on('restore', handle); - handle(); -} diff --git a/src/main/AppStateManager.js b/src/main/AppStateManager.js deleted file mode 100644 index 11c7a210..00000000 --- a/src/main/AppStateManager.js +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import JsonFileManager from '../common/JsonFileManager'; - -import * as Validator from './Validator'; - -export default class AppStateManager extends JsonFileManager { - constructor(file) { - super(file); - - // ensure data loaded from file is valid - const validatedJSON = Validator.validateAppState(this.json); - if (!validatedJSON) { - this.setJson({}); - } - } - set lastAppVersion(version) { - this.setValue('lastAppVersion', version); - } - - get lastAppVersion() { - return this.getValue('lastAppVersion'); - } - - set skippedVersion(version) { - this.setValue('skippedVersion', version); - } - - get skippedVersion() { - return this.getValue('skippedVersion'); - } - - set updateCheckedDate(date) { - this.setValue('updateCheckedDate', date.toISOString()); - } - - get updateCheckedDate() { - const date = this.getValue('updateCheckedDate'); - if (date) { - return new Date(date); - } - return null; - } -} diff --git a/src/main/AppVersionManager.js b/src/main/AppVersionManager.js new file mode 100644 index 00000000..6f4c8a1d --- /dev/null +++ b/src/main/AppVersionManager.js @@ -0,0 +1,46 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import JsonFileManager from '../common/JsonFileManager'; + +import * as Validator from './Validator'; + +export default class AppVersionManager extends JsonFileManager { + constructor(file) { + super(file); + + // ensure data loaded from file is valid + const validatedJSON = Validator.validateAppState(this.json); + if (!validatedJSON) { + this.setJson({}); + } + } + set lastAppVersion(version) { + this.setValue('lastAppVersion', version); + } + + get lastAppVersion() { + return this.getValue('lastAppVersion'); + } + + set skippedVersion(version) { + this.setValue('skippedVersion', version); + } + + get skippedVersion() { + return this.getValue('skippedVersion'); + } + + set updateCheckedDate(date) { + this.setValue('updateCheckedDate', date.toISOString()); + } + + get updateCheckedDate() { + const date = this.getValue('updateCheckedDate'); + if (date) { + return new Date(date); + } + return null; + } +} diff --git a/src/main/AutoLauncher.js b/src/main/AutoLauncher.js index 27532e19..dcc7bf43 100644 --- a/src/main/AutoLauncher.js +++ b/src/main/AutoLauncher.js @@ -5,44 +5,45 @@ import AutoLaunch from 'auto-launch'; import {app} from 'electron'; import isDev from 'electron-is-dev'; +import log from 'electron-log'; export default class AutoLauncher { - constructor() { - this.appLauncher = new AutoLaunch({ - name: app.name, - isHidden: true, - }); - } - - isEnabled() { - return this.appLauncher.isEnabled(); - } - - async blankPromise() { - return null; - } - - async enable() { - if (isDev) { - console.log('In development mode, autostart config never effects'); - return this.blankPromise(); + constructor() { + this.appLauncher = new AutoLaunch({ + name: app.name, + isHidden: true, + }); } - const enabled = await this.isEnabled(); - if (!enabled) { - return this.appLauncher.enable(); - } - return this.blankPromise(); - } - async disable() { - if (isDev) { - console.log('In development mode, autostart config never effects'); - return this.blankPromise(); + isEnabled() { + return this.appLauncher.isEnabled(); } - const enabled = await this.isEnabled(); - if (enabled) { - return this.appLauncher.disable(); + + async blankPromise() { + return null; + } + + async enable() { + if (isDev) { + log.warn('In development mode, autostart config never effects'); + return this.blankPromise(); + } + const enabled = await this.isEnabled(); + if (!enabled) { + return this.appLauncher.enable(); + } + return this.blankPromise(); + } + + async disable() { + if (isDev) { + log.warn('In development mode, autostart config never effects'); + return this.blankPromise(); + } + const enabled = await this.isEnabled(); + if (enabled) { + return this.appLauncher.disable(); + } + return this.blankPromise(); } - return this.blankPromise(); - } } diff --git a/src/main/CriticalErrorHandler.js b/src/main/CriticalErrorHandler.js index a4f13800..69b0454d 100644 --- a/src/main/CriticalErrorHandler.js +++ b/src/main/CriticalErrorHandler.js @@ -3,6 +3,7 @@ // See LICENSE.txt for license information. import {spawn} from 'child_process'; import fs from 'fs'; + import os from 'os'; import path from 'path'; @@ -15,94 +16,95 @@ const BUTTON_SHOW_DETAILS = 'Show Details'; const BUTTON_REOPEN = 'Reopen'; function createErrorReport(err) { - return `Application: ${app.name} ${app.getVersion()}\n` + + // eslint-disable-next-line no-undef + return `Application: ${app.name} ${app.getVersion()} [commit: ${__HASH_VERSION__}]\n` + `Platform: ${os.type()} ${os.release()} ${os.arch()}\n` + `${err.stack}`; } function openDetachedExternal(url) { - const spawnOption = {detached: true, stdio: 'ignore'}; - switch (process.platform) { - case 'win32': - return spawn('cmd', ['/C', 'start', url], spawnOption); - case 'darwin': - return spawn('open', [url], spawnOption); - case 'linux': - return spawn('xdg-open', [url], spawnOption); - default: - return null; - } + const spawnOption = {detached: true, stdio: 'ignore'}; + switch (process.platform) { + case 'win32': + return spawn('cmd', ['/C', 'start', url], spawnOption); + case 'darwin': + return spawn('open', [url], spawnOption); + case 'linux': + return spawn('xdg-open', [url], spawnOption); + default: + return null; + } } export default class CriticalErrorHandler { - constructor() { - this.mainWindow = null; - } - - setMainWindow(mainWindow) { - this.mainWindow = mainWindow; - } - - windowUnresponsiveHandler() { - dialog.showMessageBox(this.mainWindow, { - type: 'warning', - title: app.name, - message: 'The window is no longer responsive.\nDo you wait until the window becomes responsive again?', - buttons: ['No', 'Yes'], - defaultId: 0, - }).then(({response}) => { - if (response === 0) { - throw new Error('BrowserWindow \'unresponsive\' event has been emitted'); - } - }); - } - - processUncaughtExceptionHandler(err) { - const file = path.join(app.getPath('userData'), `uncaughtException-${Date.now()}.txt`); - const report = createErrorReport(err); - fs.writeFileSync(file, report.replace(new RegExp('\\n', 'g'), os.EOL)); - - if (app.isReady()) { - const buttons = [BUTTON_SHOW_DETAILS, BUTTON_OK, BUTTON_REOPEN]; - if (process.platform === 'darwin') { - buttons.reverse(); - } - const bindWindow = this.mainWindow && this.mainWindow.isVisible() ? this.mainWindow : null; - dialog.showMessageBox( - bindWindow, - { - type: 'error', - title: app.name, - message: `The ${app.name} app quit unexpectedly. Click "Show Details" to learn more or "Reopen" to open the application again.\n\nInternal error: ${err.message}`, - buttons, - defaultId: buttons.indexOf(BUTTON_REOPEN), - noLink: true, - } - ).then(({response}) => { - let child; - switch (response) { - case buttons.indexOf(BUTTON_SHOW_DETAILS): - child = openDetachedExternal(file); - if (child) { - child.on( - 'error', - (spawnError) => { - console.log(spawnError); - } - ); - child.unref(); - } - break; - case buttons.indexOf(BUTTON_REOPEN): - app.relaunch(); - break; - } - app.exit(-1); - }); - } else { - log.err(`Window wasn't ready to handle the error: ${err}\ntrace: ${err.stack}`); - throw err; + constructor() { + this.mainWindow = null; + } + + setMainWindow(mainWindow) { + this.mainWindow = mainWindow; + } + + windowUnresponsiveHandler() { + dialog.showMessageBox(this.mainWindow, { + type: 'warning', + title: app.name, + message: 'The window is no longer responsive.\nDo you wait until the window becomes responsive again?', + buttons: ['No', 'Yes'], + defaultId: 0, + }).then(({response}) => { + if (response === 0) { + throw new Error('BrowserWindow \'unresponsive\' event has been emitted'); + } + }); + } + + processUncaughtExceptionHandler(err) { + const file = path.join(app.getPath('userData'), `uncaughtException-${Date.now()}.txt`); + const report = createErrorReport(err); + fs.writeFileSync(file, report.replace(new RegExp('\\n', 'g'), os.EOL)); + + if (app.isReady()) { + const buttons = [BUTTON_SHOW_DETAILS, BUTTON_OK, BUTTON_REOPEN]; + if (process.platform === 'darwin') { + buttons.reverse(); + } + const bindWindow = this.mainWindow && this.mainWindow.isVisible() ? this.mainWindow : null; + dialog.showMessageBox( + bindWindow, + { + type: 'error', + title: app.name, + message: `The ${app.name} app quit unexpectedly. Click "Show Details" to learn more or "Reopen" to open the application again.\n\nInternal error: ${err.message}`, + buttons, + defaultId: buttons.indexOf(BUTTON_REOPEN), + noLink: true, + }, + ).then(({response}) => { + let child; + switch (response) { + case buttons.indexOf(BUTTON_SHOW_DETAILS): + child = openDetachedExternal(file); + if (child) { + child.on( + 'error', + (spawnError) => { + log.error(spawnError); + }, + ); + child.unref(); + } + break; + case buttons.indexOf(BUTTON_REOPEN): + app.relaunch(); + break; + } + app.exit(-1); + }); + } else { + log.err(`Window wasn't ready to handle the error: ${err}\ntrace: ${err.stack}`); + throw err; + } } - } } diff --git a/src/main/MattermostServer.js b/src/main/MattermostServer.js new file mode 100644 index 00000000..f9fc588e --- /dev/null +++ b/src/main/MattermostServer.js @@ -0,0 +1,30 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import urlUtils from 'common/utils/url'; + +export class MattermostServer { + constructor(name, serverUrl) { + this.name = name; + this.url = urlUtils.parseURL(serverUrl); + if (!this.url) { + throw new Error('Invalid url for creating a server'); + } + } + + getServerInfo = () => { + // does the server have a subpath? + const normalizedPath = this.url.pathname.toLowerCase(); + const subpath = normalizedPath.endsWith('/') ? normalizedPath : `${normalizedPath}/`; + return {origin: this.url.origin, subpath, url: this.url.toString()}; + } + + sameOrigin = (otherURL) => { + const parsedUrl = urlUtils.parseURL(otherURL); + return parsedUrl && this.url.origin === parsedUrl.origin; + } + + equals = (otherServer) => { + return (this.name === otherServer.name) && (this.url.toString() === otherServer.url.toString()); + } +} diff --git a/src/main/ParseArgs.js b/src/main/ParseArgs.js index 659c979a..dd7872c6 100644 --- a/src/main/ParseArgs.js +++ b/src/main/ParseArgs.js @@ -8,30 +8,30 @@ import {protocols} from '../../electron-builder.json'; import * as Validator from './Validator'; export default function parse(args) { - return validateArgs(parseArgs(triageArgs(args))); + return validateArgs(parseArgs(triageArgs(args))); } function triageArgs(args) { - // ensure any args following a possible deeplink are discarded - if (protocols && protocols[0] && protocols[0].schemes && protocols[0].schemes[0]) { - const scheme = protocols[0].schemes[0].toLowerCase(); - const deeplinkIndex = args.findIndex((arg) => arg.toLowerCase().includes(`${scheme}:`)); - if (deeplinkIndex !== -1) { - return args.slice(0, deeplinkIndex + 1); + // ensure any args following a possible deeplink are discarded + if (protocols && protocols[0] && protocols[0].schemes && protocols[0].schemes[0]) { + const scheme = protocols[0].schemes[0].toLowerCase(); + const deeplinkIndex = args.findIndex((arg) => arg.toLowerCase().includes(`${scheme}:`)); + if (deeplinkIndex !== -1) { + return args.slice(0, deeplinkIndex + 1); + } } - } - return args; + return args; } function parseArgs(args) { - return yargs. - alias('dataDir', 'd').string('dataDir').describe('dataDir', 'Set the path to where user data is stored.'). - alias('disableDevMode', 'p').boolean('disableDevMode').describe('disableDevMode', 'Disable development mode. Allows for testing as if it was Production.'). - alias('version', 'v').boolean('version').describe('version', 'Prints the application version.'). - help('help'). - parse(args); + return yargs. + alias('dataDir', 'd').string('dataDir').describe('dataDir', 'Set the path to where user data is stored.'). + alias('disableDevMode', 'p').boolean('disableDevMode').describe('disableDevMode', 'Disable development mode. Allows for testing as if it was Production.'). + alias('version', 'v').boolean('version').describe('version', 'Prints the application version.'). + help('help'). + parse(args); } function validateArgs(args) { - return Validator.validateArgs(args) || {}; + return Validator.validateArgs(args) || {}; } diff --git a/src/main/SpellChecker.js b/src/main/SpellChecker.js deleted file mode 100644 index c5a0f763..00000000 --- a/src/main/SpellChecker.js +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -'use strict'; - -import EventEmitter from 'events'; - -import simpleSpellChecker from 'simple-spellchecker'; - -/// Following approach for contractions is derived from electron-spellchecker. - -// NB: This is to work around electron/electron#1005, where contractions -// are incorrectly marked as spelling errors. This lets people get away with -// incorrectly spelled contracted words, but it's the best we can do for now. -const contractions = [ - "ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't", - "hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd", - "I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't", - "mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've", - "she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've", - "there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've", - "we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd", - "where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't", - "would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've", -]; - -const contractionMap = contractions.reduce((acc, word) => { - acc[word.replace(/'.*/, '')] = true; - return acc; -}, {}); - -/// End: derived from electron-spellchecker. - -export default class SpellChecker extends EventEmitter { - constructor(locale, dictDir, callback) { - super(); - this.dict = null; - this.locale = locale; - simpleSpellChecker.getDictionary(locale, dictDir, (err, dict) => { - if (err) { - this.emit('error', err); - if (callback) { - callback(err); - } - } else { - this.dict = dict; - this.emit('ready'); - if (callback) { - callback(null, this); - } - } - }); - } - - isReady() { - return this.dict !== null; - } - - spellCheck(word) { - if (word.toLowerCase() === 'mattermost') { - return true; - } - if (isFinite(word)) { // Numerals are not included in the dictionary - return true; - } - if (this.locale.match(/^en-?/) && contractionMap[word]) { - return true; - } - return this.dict.spellCheck(word); - } - - getSuggestions(word, maxSuggestions) { - const suggestions = this.dict.getSuggestions(word, maxSuggestions); - - const firstCharWord = word.charAt(0); - let i; - for (i = 0; i < suggestions.length; i++) { - if (suggestions[i].charAt(0).toUpperCase() === firstCharWord.toUpperCase()) { - suggestions[i] = firstCharWord + suggestions[i].slice(1); - } - } - - const uniqueSuggestions = suggestions.reduce((a, b) => { - if (a.indexOf(b) < 0) { - a.push(b); - } - return a; - }, []); - - return uniqueSuggestions; - } -} - -SpellChecker.getSpellCheckerLocale = (electronLocale) => { - if (electronLocale.match(/^en-?/)) { - return 'en-US'; - } - if (electronLocale.match(/^fr-?/)) { - return 'fr-FR'; - } - if (electronLocale.match(/^de-?/)) { - return 'de-DE'; - } - if (electronLocale.match(/^es-?/)) { - return 'es-ES'; - } - if (electronLocale.match(/^nl-?/)) { - return 'nl-NL'; - } - if (electronLocale.match(/^pl-?/)) { - return 'pl-PL'; - } - if (electronLocale.match(/^pt-?/)) { - return 'pt-BR'; - } - if (electronLocale.match(/^it-?/)) { - return 'it-IT'; - } - if (electronLocale.match(/^ru-?/)) { - return 'ru-RU'; - } - if (electronLocale.match(/^sv-?/)) { - return 'sv-SE'; - } - if (electronLocale.match(/^uk-?/)) { - return 'uk-UA'; - } - return 'en-US'; -}; diff --git a/src/main/UserActivityMonitor.js b/src/main/UserActivityMonitor.js index d309dbce..65f1140b 100644 --- a/src/main/UserActivityMonitor.js +++ b/src/main/UserActivityMonitor.js @@ -4,6 +4,7 @@ import EventEmitter from 'events'; import electron from 'electron'; +import log from 'electron-log'; const {app} = electron; @@ -11,30 +12,30 @@ const {app} = electron; * Monitors system idle time, listens for system events and fires status updates as needed */ export default class UserActivityMonitor extends EventEmitter { - constructor() { - super(); + constructor() { + super(); - this.isActive = true; - this.idleTime = 0; - this.lastSetActive = null; - this.systemIdleTimeIntervalID = -1; + this.isActive = true; + this.idleTime = 0; + this.lastSetActive = null; + this.systemIdleTimeIntervalID = -1; - this.config = { - updateFrequencyMs: 1 * 1000, // eslint-disable-line no-magic-numbers - inactiveThresholdMs: 60 * 1000, // eslint-disable-line no-magic-numbers - statusUpdateThresholdMs: 60 * 1000, // eslint-disable-line no-magic-numbers - }; - } + this.config = { + updateFrequencyMs: 1 * 1000, // eslint-disable-line no-magic-numbers + inactiveThresholdMs: 60 * 1000, // eslint-disable-line no-magic-numbers + statusUpdateThresholdMs: 60 * 1000, // eslint-disable-line no-magic-numbers + }; + } - get userIsActive() { - return this.isActive; - } + get userIsActive() { + return this.isActive; + } - get userIdleTime() { - return this.idleTime; - } + get userIdleTime() { + return this.idleTime; + } - /** + /** * Begin monitoring system events and idle time at defined frequency * * @param {Object} config - overide internal configuration defaults @@ -44,86 +45,86 @@ export default class UserActivityMonitor extends EventEmitter { * @emits {error} emitted when method is called before the app is ready * @emits {error} emitted when this method has previously been called but not subsequently stopped */ - startMonitoring(config = {}) { - if (!app.isReady()) { - this.emit('error', new Error('UserActivityMonitor.startMonitoring can only be called after app is ready')); - return; + startMonitoring(config = {}) { + if (!app.isReady()) { + this.emit('error', new Error('UserActivityMonitor.startMonitoring can only be called after app is ready')); + return; + } + + if (this.systemIdleTimeIntervalID >= 0) { + this.emit('error', new Error('User activity monitoring is already in progress')); + return; + } + + this.config = Object.assign({}, this.config, config); + + this.systemIdleTimeIntervalID = setInterval(() => { + try { + this.updateIdleTime(electron.powerMonitor.getSystemIdleTime()); + } catch (err) { + log.error('Error getting system idle time:', err); + } + }, this.config.updateFrequencyMs); } - if (this.systemIdleTimeIntervalID >= 0) { - this.emit('error', new Error('User activity monitoring is already in progress')); - return; - } - - this.config = Object.assign({}, this.config, config); - - this.systemIdleTimeIntervalID = setInterval(() => { - try { - this.updateIdleTime(electron.powerMonitor.getSystemIdleTime()); - } catch (err) { - console.log('Error getting system idle time:', err); - } - }, this.config.updateFrequencyMs); - } - - /** + /** * Stop monitoring system events and idle time */ - stopMonitoring() { - clearInterval(this.systemIdleTimeIntervalID); - } + stopMonitoring() { + clearInterval(this.systemIdleTimeIntervalID); + } - /** + /** * Updates internal idle time and sets internal user activity state * * @param {integer} idleTime * @private */ - updateIdleTime(idleTime) { - this.idleTime = idleTime; - if (idleTime * 1000 > this.config.inactiveThresholdMs) { // eslint-disable-line no-magic-numbers - this.setActivityState(false); - } else { - this.setActivityState(true); + updateIdleTime(idleTime) { + this.idleTime = idleTime; + if (idleTime * 1000 > this.config.inactiveThresholdMs) { // eslint-disable-line no-magic-numbers + this.setActivityState(false); + } else { + this.setActivityState(true); + } } - } - /** + /** * Updates user active state and conditionally triggers a status update * * @param {boolean} isActive * @param {boolean} isSystemEvent – indicates whether the update was triggered by a system event (log in/out, screesaver on/off etc) * @private */ - setActivityState(isActive = false, isSystemEvent = false) { - this.isActive = isActive; + setActivityState(isActive = false, isSystemEvent = false) { + this.isActive = isActive; - if (isSystemEvent) { - this.sendStatusUpdate(true); - return; + if (isSystemEvent) { + this.sendStatusUpdate(true); + return; + } + + const now = Date.now(); + + if (isActive && (this.lastSetActive == null || now - this.lastSetActive >= this.config.statusUpdateThresholdMs)) { + this.sendStatusUpdate(false); + this.lastSetActive = now; + } else if (!isActive) { + this.lastSetActive = null; + } } - const now = Date.now(); - - if (isActive && (this.lastSetActive == null || now - this.lastSetActive >= this.config.statusUpdateThresholdMs)) { - this.sendStatusUpdate(false); - this.lastSetActive = now; - } else if (!isActive) { - this.lastSetActive = null; - } - } - - /** + /** * Sends an update with user activity status and current system idle time * * @emits {status} emitted at regular, definable intervals providing an update on user active status and idle time * @private */ - sendStatusUpdate(isSystemEvent = false) { - this.emit('status', { - userIsActive: this.isActive, - idleTime: this.idleTime, - isSystemEvent, - }); - } + sendStatusUpdate(isSystemEvent = false) { + this.emit('status', { + userIsActive: this.isActive, + idleTime: this.idleTime, + isSystemEvent, + }); + } } diff --git a/src/main/Validator.js b/src/main/Validator.js index 9894d429..e56164a5 100644 --- a/src/main/Validator.js +++ b/src/main/Validator.js @@ -1,11 +1,13 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import log from 'electron-log'; + import Joi from '@hapi/joi'; -import urlUtils from '../utils/url'; +import urlUtils from 'common/utils/url'; const defaultOptions = { - stripUnknown: true, + stripUnknown: true, }; const defaultWindowWidth = 1000; const defaultWindowHeight = 700; @@ -13,193 +15,193 @@ const minWindowWidth = 400; const minWindowHeight = 240; const argsSchema = Joi.object({ - hidden: Joi.boolean(), - disableDevMode: Joi.boolean(), - dataDir: Joi.string(), - version: Joi.boolean(), + hidden: Joi.boolean(), + disableDevMode: Joi.boolean(), + dataDir: Joi.string(), + version: Joi.boolean(), }); const boundsInfoSchema = Joi.object({ - x: Joi.number().integer().default(0), - y: Joi.number().integer().default(0), - width: Joi.number().integer().min(minWindowWidth).required().default(defaultWindowWidth), - height: Joi.number().integer().min(minWindowHeight).required().default(defaultWindowHeight), - maximized: Joi.boolean().default(false), - fullscreen: Joi.boolean().default(false), + x: Joi.number().integer().default(0), + y: Joi.number().integer().default(0), + width: Joi.number().integer().min(minWindowWidth).required().default(defaultWindowWidth), + height: Joi.number().integer().min(minWindowHeight).required().default(defaultWindowHeight), + maximized: Joi.boolean().default(false), + fullscreen: Joi.boolean().default(false), }); const appStateSchema = Joi.object({ - lastAppVersion: Joi.string(), - skippedVersion: Joi.string(), - updateCheckedDate: Joi.string(), + lastAppVersion: Joi.string(), + skippedVersion: Joi.string(), + updateCheckedDate: Joi.string(), }); const configDataSchemaV0 = Joi.object({ - url: Joi.string().required(), + url: Joi.string().required(), }); const configDataSchemaV1 = Joi.object({ - version: Joi.number().min(1).default(1), - teams: Joi.array().items(Joi.object({ - name: Joi.string().required(), - url: Joi.string().required(), - })).default([]), - showTrayIcon: Joi.boolean().default(false), - trayIconTheme: Joi.any().allow('').valid('light', 'dark').default('light'), - minimizeToTray: Joi.boolean().default(false), - notifications: Joi.object({ - flashWindow: Joi.any().valid(0, 2).default(0), - bounceIcon: Joi.boolean().default(false), - bounceIconType: Joi.any().allow('').valid('informational', 'critical').default('informational'), - }), - showUnreadBadge: Joi.boolean().default(true), - useSpellChecker: Joi.boolean().default(true), - enableHardwareAcceleration: Joi.boolean().default(true), - autostart: Joi.boolean().default(true), - spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'), + version: Joi.number().min(1).default(1), + teams: Joi.array().items(Joi.object({ + name: Joi.string().required(), + url: Joi.string().required(), + })).default([]), + showTrayIcon: Joi.boolean().default(false), + trayIconTheme: Joi.any().allow('').valid('light', 'dark').default('light'), + minimizeToTray: Joi.boolean().default(false), + notifications: Joi.object({ + flashWindow: Joi.any().valid(0, 2).default(0), + bounceIcon: Joi.boolean().default(false), + bounceIconType: Joi.any().allow('').valid('informational', 'critical').default('informational'), + }), + showUnreadBadge: Joi.boolean().default(true), + useSpellChecker: Joi.boolean().default(true), + enableHardwareAcceleration: Joi.boolean().default(true), + autostart: Joi.boolean().default(true), + spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'), }); const configDataSchemaV2 = Joi.object({ - version: Joi.number().min(2).default(2), - teams: Joi.array().items(Joi.object({ - name: Joi.string().required(), - url: Joi.string().required(), - order: Joi.number().integer().min(0), - })).default([]), - showTrayIcon: Joi.boolean().default(false), - trayIconTheme: Joi.any().allow('').valid('light', 'dark').default('light'), - minimizeToTray: Joi.boolean().default(false), - notifications: Joi.object({ - flashWindow: Joi.any().valid(0, 2).default(0), - bounceIcon: Joi.boolean().default(false), - bounceIconType: Joi.any().allow('').valid('informational', 'critical').default('informational'), - }), - showUnreadBadge: Joi.boolean().default(true), - useSpellChecker: Joi.boolean().default(true), - enableHardwareAcceleration: Joi.boolean().default(true), - autostart: Joi.boolean().default(true), - spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'), - darkMode: Joi.boolean().default(false), - downloadLocation: Joi.string(), + version: Joi.number().min(2).default(2), + teams: Joi.array().items(Joi.object({ + name: Joi.string().required(), + url: Joi.string().required(), + order: Joi.number().integer().min(0), + })).default([]), + showTrayIcon: Joi.boolean().default(false), + trayIconTheme: Joi.any().allow('').valid('light', 'dark').default('light'), + minimizeToTray: Joi.boolean().default(false), + notifications: Joi.object({ + flashWindow: Joi.any().valid(0, 2).default(0), + bounceIcon: Joi.boolean().default(false), + bounceIconType: Joi.any().allow('').valid('informational', 'critical').default('informational'), + }), + showUnreadBadge: Joi.boolean().default(true), + useSpellChecker: Joi.boolean().default(true), + enableHardwareAcceleration: Joi.boolean().default(true), + autostart: Joi.boolean().default(true), + spellCheckerLocale: Joi.string().regex(/^[a-z]{2}-[A-Z]{2}$/).default('en-US'), + darkMode: Joi.boolean().default(false), + downloadLocation: Joi.string(), }); // eg. data['community.mattermost.com'] = { data: 'certificate data', issuerName: 'COMODO RSA Domain Validation Secure Server CA'}; const certificateStoreSchema = Joi.object().pattern( - Joi.string().uri(), - Joi.object({ - data: Joi.string(), - issuerName: Joi.string(), - }) + Joi.string().uri(), + Joi.object({ + data: Joi.string(), + issuerName: Joi.string(), + }), ); const originPermissionsSchema = Joi.object().keys({ - canBasicAuth: Joi.boolean().default(false), // we can add more permissions later if we want + canBasicAuth: Joi.boolean().default(false), // we can add more permissions later if we want }); const trustedOriginsSchema = Joi.object({}).pattern( - Joi.string().uri(), - Joi.object().keys({ - canBasicAuth: Joi.boolean().default(false), // we can add more permissions later if we want - }), + Joi.string().uri(), + Joi.object().keys({ + canBasicAuth: Joi.boolean().default(false), // we can add more permissions later if we want + }), ); const allowedProtocolsSchema = Joi.array().items(Joi.string().regex(/^[a-z-]+:$/i)); // validate bounds_info.json export function validateArgs(data) { - return validateAgainstSchema(data, argsSchema); + return validateAgainstSchema(data, argsSchema); } // validate bounds_info.json export function validateBoundsInfo(data) { - return validateAgainstSchema(data, boundsInfoSchema); + return validateAgainstSchema(data, boundsInfoSchema); } // validate app_state.json export function validateAppState(data) { - return validateAgainstSchema(data, appStateSchema); + return validateAgainstSchema(data, appStateSchema); } // validate v.0 config.json export function validateV0ConfigData(data) { - return validateAgainstSchema(data, configDataSchemaV0); + return validateAgainstSchema(data, configDataSchemaV0); } // validate v.1 config.json export function validateV1ConfigData(data) { - if (Array.isArray(data.teams) && data.teams.length) { + if (Array.isArray(data.teams) && data.teams.length) { // first replace possible backslashes with forward slashes - let teams = data.teams.map(({name, url}) => { - let updatedURL = url; - if (updatedURL.includes('\\')) { - updatedURL = updatedURL.toLowerCase().replace(/\\/gi, '/'); - } - return {name, url: updatedURL}; - }); + let teams = data.teams.map(({name, url}) => { + let updatedURL = url; + if (updatedURL.includes('\\')) { + updatedURL = updatedURL.toLowerCase().replace(/\\/gi, '/'); + } + return {name, url: updatedURL}; + }); - // next filter out urls that are still invalid so all is not lost - teams = teams.filter(({url}) => urlUtils.isValidURL(url)); + // next filter out urls that are still invalid so all is not lost + teams = teams.filter(({url}) => urlUtils.isValidURL(url)); - // replace original teams - data.teams = teams; - } - return validateAgainstSchema(data, configDataSchemaV1); + // replace original teams + data.teams = teams; + } + return validateAgainstSchema(data, configDataSchemaV1); } export function validateV2ConfigData(data) { - if (Array.isArray(data.teams) && data.teams.length) { + if (Array.isArray(data.teams) && data.teams.length) { // first replace possible backslashes with forward slashes - let teams = data.teams.map(({name, url, order}) => { - let updatedURL = url; - if (updatedURL.includes('\\')) { - updatedURL = updatedURL.toLowerCase().replace(/\\/gi, '/'); - } - return {name, url: updatedURL, order}; - }); + let teams = data.teams.map(({name, url, order}) => { + let updatedURL = url; + if (updatedURL.includes('\\')) { + updatedURL = updatedURL.toLowerCase().replace(/\\/gi, '/'); + } + return {name, url: updatedURL, order}; + }); - // next filter out urls that are still invalid so all is not lost - teams = teams.filter(({url}) => urlUtils.isValidURL(url)); + // next filter out urls that are still invalid so all is not lost + teams = teams.filter(({url}) => urlUtils.isValidURL(url)); - // replace original teams - data.teams = teams; - } - return validateAgainstSchema(data, configDataSchemaV2); + // replace original teams + data.teams = teams; + } + return validateAgainstSchema(data, configDataSchemaV2); } // validate certificate.json export function validateCertificateStore(data) { - const jsonData = (typeof data === 'object' ? data : JSON.parse(data)); - return validateAgainstSchema(jsonData, certificateStoreSchema); + const jsonData = (typeof data === 'object' ? data : JSON.parse(data)); + return validateAgainstSchema(jsonData, certificateStoreSchema); } // validate allowedProtocols.json export function validateAllowedProtocols(data) { - return validateAgainstSchema(data, allowedProtocolsSchema); + return validateAgainstSchema(data, allowedProtocolsSchema); } export function validateTrustedOriginsStore(data) { - const jsonData = (typeof data === 'object' ? data : JSON.parse(data)); - return validateAgainstSchema(jsonData, trustedOriginsSchema); + const jsonData = (typeof data === 'object' ? data : JSON.parse(data)); + return validateAgainstSchema(jsonData, trustedOriginsSchema); } export function validateOriginPermissions(data) { - const jsonData = (typeof data === 'object' ? data : JSON.parse(data)); - return validateAgainstSchema(jsonData, originPermissionsSchema); + const jsonData = (typeof data === 'object' ? data : JSON.parse(data)); + return validateAgainstSchema(jsonData, originPermissionsSchema); } function validateAgainstSchema(data, schema) { - if (typeof data !== 'object') { - console.error(`Input 'data' is not an object we can validate: ${typeof data}`); - return false; - } - if (!schema) { - console.error('No schema provided to validate'); - return false; - } - const {error, value} = schema.validate(data, defaultOptions); - if (error) { - console.error(`Validation failed due to: ${error}`); - return false; - } - return value; + if (typeof data !== 'object') { + log.error(`Input 'data' is not an object we can validate: ${typeof data}`); + return false; + } + if (!schema) { + log.error('No schema provided to validate'); + return false; + } + const {error, value} = schema.validate(data, defaultOptions); + if (error) { + log.error(`Validation failed due to: ${error}`); + return false; + } + return value; } diff --git a/src/main/allowProtocolDialog.js b/src/main/allowProtocolDialog.js index cb8769d8..2e32fcd5 100644 --- a/src/main/allowProtocolDialog.js +++ b/src/main/allowProtocolDialog.js @@ -3,83 +3,85 @@ // See LICENSE.txt for license information. 'use strict'; -import path from 'path'; import fs from 'fs'; -import {app, dialog, ipcMain, shell} from 'electron'; +import path from 'path'; + +import {app, dialog, shell} from 'electron'; +import log from 'electron-log'; import {protocols} from '../../electron-builder.json'; import * as Validator from './Validator'; +import {getMainWindow} from './windows/windowManager'; const allowedProtocolFile = path.resolve(app.getPath('userData'), 'allowedProtocols.json'); let allowedProtocols = []; function addScheme(scheme) { - const proto = `${scheme}:`; - if (!allowedProtocols.includes(proto)) { - allowedProtocols.push(proto); - } + const proto = `${scheme}:`; + if (!allowedProtocols.includes(proto)) { + allowedProtocols.push(proto); + } } -function init(mainWindow) { - fs.readFile(allowedProtocolFile, 'utf-8', (err, data) => { - if (!err) { - allowedProtocols = JSON.parse(data); - allowedProtocols = Validator.validateAllowedProtocols(allowedProtocols) || []; - } - addScheme('http'); - addScheme('https'); - protocols.forEach((protocol) => { - if (protocol.schemes && protocol.schemes.length > 0) { - protocol.schemes.forEach(addScheme); - } - }); - initDialogEvent(mainWindow); - }); -} - -function initDialogEvent(mainWindow) { - ipcMain.on('confirm-protocol', (event, protocol, URL) => { - if (allowedProtocols.indexOf(protocol) !== -1) { - shell.openExternal(URL); - return; - } - dialog.showMessageBox(mainWindow, { - title: 'Non http(s) protocol', - message: `${protocol} link requires an external application.`, - detail: `The requested link is ${URL} . Do you want to continue?`, - type: 'warning', - buttons: [ - 'Yes', - `Yes (Save ${protocol} as allowed)`, - 'No', - ], - cancelId: 2, - noLink: true, - }).then(({response}) => { - switch (response) { - case 1: { - allowedProtocols.push(protocol); - function handleError(err) { - if (err) { - console.error(err); - } +function init() { + fs.readFile(allowedProtocolFile, 'utf-8', (err, data) => { + if (!err) { + allowedProtocols = JSON.parse(data); + allowedProtocols = Validator.validateAllowedProtocols(allowedProtocols) || []; + } + addScheme('http'); + addScheme('https'); + protocols.forEach((protocol) => { + if (protocol.schemes && protocol.schemes.length > 0) { + protocol.schemes.forEach(addScheme); + } + }); + }); +} + +function handleDialogEvent(protocol, URL) { + if (allowedProtocols.indexOf(protocol) !== -1) { + shell.openExternal(URL); + return; + } + dialog.showMessageBox(getMainWindow(), { + title: 'Non http(s) protocol', + message: `${protocol} link requires an external application.`, + detail: `The requested link is ${URL} . Do you want to continue?`, + defaultId: 2, + type: 'warning', + buttons: [ + 'Yes', + `Yes (Save ${protocol} as allowed)`, + 'No', + ], + cancelId: 2, + noLink: true, + }).then(({response}) => { + switch (response) { + case 1: { + allowedProtocols.push(protocol); + function handleError(err) { + if (err) { + log.error(err); + } + } + fs.writeFile(allowedProtocolFile, JSON.stringify(allowedProtocols), handleError); + shell.openExternal(URL); + break; + } + case 0: + shell.openExternal(URL); + break; + default: + break; } - fs.writeFile(allowedProtocolFile, JSON.stringify(allowedProtocols), handleError); - shell.openExternal(URL); - break; - } - case 0: - shell.openExternal(URL); - break; - default: - break; - } }); - }); } export default { - init, + init, + handleDialogEvent, }; diff --git a/src/main/appState.js b/src/main/appState.js new file mode 100644 index 00000000..dc3be8ef --- /dev/null +++ b/src/main/appState.js @@ -0,0 +1,120 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import events from 'events'; +import {ipcMain} from 'electron'; + +import {UPDATE_MENTIONS, UPDATE_TRAY, UPDATE_BADGE, SESSION_EXPIRED} from 'common/communication'; + +import * as WindowManager from './windows/windowManager'; + +const status = { + unreads: new Map(), + mentions: new Map(), + expired: new Map(), + emitter: new events.EventEmitter(), +}; + +const emitMentions = (serverName) => { + const newMentions = getMentions(serverName); + const newUnreads = getUnreads(serverName); + const isExpired = getIsExpired(serverName); + + WindowManager.sendToRenderer(UPDATE_MENTIONS, serverName, newMentions, newUnreads, isExpired); + emitStatus(); +}; + +const emitTray = (expired, mentions, unreads) => { + status.emitter.emit(UPDATE_TRAY, expired, Boolean(mentions), unreads); +}; + +const emitBadge = (expired, mentions, unreads) => { + status.emitter.emit(UPDATE_BADGE, expired, mentions, unreads); +}; + +const emitStatus = () => { + const expired = anyExpired(); + const mentions = totalMentions(); + const unreads = anyUnreads(); + emitTray(expired, mentions, unreads); + emitBadge(expired, mentions, unreads); +}; + +export const updateMentions = (serverName, mentions, unreads) => { + if (typeof unreads !== 'undefined') { + status.unreads.set(serverName, Boolean(unreads)); + } + status.mentions.set(serverName, mentions || 0); + emitMentions(serverName); +}; + +export const updateUnreads = (serverName, unreads) => { + status.unreads.set(serverName, Boolean(unreads)); + emitMentions(serverName); +}; + +export const getUnreads = (serverName) => { + return status.unreads.get(serverName) || false; +}; + +export const getMentions = (serverName) => { + return status.mentions.get(serverName) || 0; // this might be undefined as a way to tell that we don't know as it might need to login still. +}; + +export const getIsExpired = (serverName) => { + return status.expired.get(serverName) || false; +}; + +export const anyMentions = () => { + for (const v of status.mentions.values()) { + if (v > 0) { + return v; + } + } + return false; +}; + +export const totalMentions = () => { + let total = 0; + for (const v of status.mentions.values()) { + total += v; + } + return total; +}; + +export const anyUnreads = () => { + for (const v of status.unreads.values()) { + if (v) { + return v; + } + } + return false; +}; + +export const anyExpired = () => { + for (const v of status.expired.values()) { + if (v) { + return v; + } + } + return false; +}; + +// add any other event emitter methods if needed +export const on = (event, listener) => { + status.emitter.on(event, listener); +}; + +export const setSessionExpired = (serverName, expired) => { + const isExpired = Boolean(expired); + const old = status.expired.get(serverName); + status.expired.set(serverName, isExpired); + if (typeof old !== 'undefined' && old !== isExpired) { + emitTray(); + } + emitMentions(serverName); +}; + +ipcMain.on(SESSION_EXPIRED, (event, isExpired, serverName) => { + setSessionExpired(serverName, isExpired); +}); diff --git a/src/main/authManager.js b/src/main/authManager.js new file mode 100644 index 00000000..5bc1bbd9 --- /dev/null +++ b/src/main/authManager.js @@ -0,0 +1,90 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import log from 'electron-log'; + +import {BASIC_AUTH_PERMISSION} from 'common/permissions'; +import urlUtils from 'common/utils/url'; + +import * as WindowManager from './windows/windowManager'; + +import {addModal} from './views/modalManager'; +import {getLocalURLString, getLocalPreload} from './utils'; + +const modalPreload = getLocalPreload('modalPreload.js'); +const loginModalHtml = getLocalURLString('loginModal.html'); +const permissionModalHtml = getLocalURLString('permissionModal.html'); + +export class AuthManager { + constructor(config, trustedOriginsStore) { + this.config = config; + this.trustedOriginsStore = trustedOriginsStore; + this.loginCallbackMap = new Map(); + + config.on('update', this.handleConfigUpdate); + } + + handleConfigUpdate = (newConfig) => { + this.config = newConfig; + } + + handleAppLogin = (event, webContents, request, authInfo, callback) => { + event.preventDefault(); + const parsedURL = new URL(request.url); + const server = urlUtils.getServer(parsedURL, this.config.teams); + + this.loginCallbackMap.set(request.url, typeof callback === 'undefined' ? null : callback); // if callback is undefined set it to null instead so we know we have set it up with no value + if (urlUtils.isTrustedURL(request.url, this.config.teams) || urlUtils.isCustomLoginURL(parsedURL, server, this.config.teams) || this.trustedOriginsStore.checkPermission(request.url, BASIC_AUTH_PERMISSION)) { + this.popLoginModal(request, authInfo); + } else { + this.popPermissionModal(request, authInfo, BASIC_AUTH_PERMISSION); + } + } + + popLoginModal = (request, authInfo) => { + const modalPromise = addModal(`login-${request.url}`, loginModalHtml, modalPreload, {request, authInfo}, WindowManager.getMainWindow()); + modalPromise.then((data) => { + const {username, password} = data; + this.handleLoginCredentialsEvent(request, username, password); + }).catch((err) => { + if (err) { + log.error('Error processing login request', err); + } + this.handleCancelLoginEvent(request); + }); + } + + popPermissionModal = (request, authInfo, permission) => { + const modalPromise = addModal(`permission-${request.url}`, permissionModalHtml, modalPreload, {url: request.url, permission}, WindowManager.getMainWindow()); + modalPromise.then(() => { + this.handlePermissionGranted(request.url, permission); + this.addToLoginQueue(request, authInfo); + }).catch((err) => { + if (err) { + log.error('Error processing permission request', err); + } + this.handleCancelLoginEvent(request); + }); + } + + handleLoginCredentialsEvent = (request, username, password) => { + const callback = this.loginCallbackMap.get(request.url); + if (typeof callback === 'undefined') { + log.error(`Failed to retrieve login callback for ${request.url}`); + return; + } + if (callback != null) { + callback(username, password); + } + this.loginCallbackMap.delete(request.url); + } + + handleCancelLoginEvent = (request) => { + log.info(`Cancelling request for ${request ? request.url : 'unknown'}`); + this.handleLoginCredentialsEvent(request); // we use undefined to cancel the request + } + + handlePermissionGranted(url, permission) { + this.trustedOriginsStore.addPermission(url, permission); + this.trustedOriginsStore.save(); + } +} diff --git a/src/main/autoLaunch.js b/src/main/autoLaunch.js index 7d69c659..4614ad09 100644 --- a/src/main/autoLaunch.js +++ b/src/main/autoLaunch.js @@ -4,16 +4,16 @@ import AutoLaunch from 'auto-launch'; async function upgradeAutoLaunch() { - if (process.platform === 'darwin') { - return; - } - const appLauncher = new AutoLaunch({ - name: 'Mattermost', - }); - const enabled = await appLauncher.isEnabled(); - if (enabled) { - await appLauncher.enable(); - } + if (process.platform === 'darwin') { + return; + } + const appLauncher = new AutoLaunch({ + name: 'Mattermost', + }); + const enabled = await appLauncher.isEnabled(); + if (enabled) { + await appLauncher.enable(); + } } export default upgradeAutoLaunch; diff --git a/src/main/autoUpdater.js b/src/main/autoUpdater.js index 10ea8d39..ca250f7a 100644 --- a/src/main/autoUpdater.js +++ b/src/main/autoUpdater.js @@ -6,190 +6,190 @@ import path from 'path'; import {app, BrowserWindow, dialog, ipcMain, shell} from 'electron'; -import logger from 'electron-log'; +import log from 'electron-log'; import {autoUpdater, CancellationToken} from 'electron-updater'; import semver from 'semver'; // eslint-disable-next-line no-magic-numbers const UPDATER_INTERVAL_IN_MS = 48 * 60 * 60 * 1000; // 48 hours -autoUpdater.logger = logger; -autoUpdater.logger.transports.file.level = 'info'; +autoUpdater.log = log; +autoUpdater.log.transports.file.level = 'info'; let updaterModal = null; function createEventListener(win, eventName) { - return (event) => { - if (event.sender === win.webContents) { - win.emit(eventName); - } - }; + return (event) => { + if (event.sender === win.webContents) { + win.emit(eventName); + } + }; } function createUpdaterModal(parentWindow, options) { - const windowWidth = 480; - const windowHeight = 280; - const windowOptions = { - title: `${app.name} Updater`, - parent: parentWindow, - modal: true, - maximizable: false, - show: false, - width: windowWidth, - height: windowHeight, - resizable: false, - autoHideMenuBar: true, - backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do - }; - if (process.platform === 'linux') { - windowOptions.icon = options.linuxAppIcon; - } + const windowWidth = 480; + const windowHeight = 280; + const windowOptions = { + title: `${app.name} Updater`, + parent: parentWindow, + modal: true, + maximizable: false, + show: false, + width: windowWidth, + height: windowHeight, + resizable: false, + autoHideMenuBar: true, + backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do + }; + if (process.platform === 'linux') { + windowOptions.icon = options.linuxAppIcon; + } - const modal = new BrowserWindow(windowOptions); - modal.once('ready-to-show', () => { - modal.show(); - }); - let updaterURL = (global.isDev ? 'http://localhost:8080' : `file://${app.getAppPath()}`) + '/browser/updater.html'; - - if (options.notifyOnly) { - updaterURL += '?notifyOnly=true'; - } - modal.loadURL(updaterURL); - - for (const eventName of ['click-release-notes', 'click-skip', 'click-remind', 'click-install', 'click-download', 'click-cancel']) { - const listener = createEventListener(modal, eventName); - ipcMain.on(eventName, listener); - modal.on('closed', () => { - ipcMain.removeListener(eventName, listener); + const modal = new BrowserWindow(windowOptions); + modal.once('ready-to-show', () => { + modal.show(); }); - } + let updaterURL = (global.isDev ? 'http://localhost:8080' : `file://${app.getAppPath()}`) + '/browser/updater.html'; - return modal; + if (options.notifyOnly) { + updaterURL += '?notifyOnly=true'; + } + modal.loadURL(updaterURL); + + for (const eventName of ['click-release-notes', 'click-skip', 'click-remind', 'click-install', 'click-download', 'click-cancel']) { + const listener = createEventListener(modal, eventName); + ipcMain.on(eventName, listener); + modal.on('closed', () => { + ipcMain.removeListener(eventName, listener); + }); + } + + return modal; } function isUpdateApplicable(now, skippedVersion, updateInfo) { - const releaseTime = new Date(updateInfo.releaseDate).getTime(); + const releaseTime = new Date(updateInfo.releaseDate).getTime(); - // 48 hours after a new version is added to releases.mattermost.com, user receives a “New update is available” dialog - if (now.getTime() - releaseTime < UPDATER_INTERVAL_IN_MS) { - return false; - } + // 48 hours after a new version is added to releases.mattermost.com, user receives a “New update is available” dialog + if (now.getTime() - releaseTime < UPDATER_INTERVAL_IN_MS) { + return false; + } - // If a version was skipped, compare version. - if (skippedVersion) { - return semver.gt(updateInfo.version, skippedVersion); - } + // If a version was skipped, compare version. + if (skippedVersion) { + return semver.gt(updateInfo.version, skippedVersion); + } - return true; + return true; } function downloadAndInstall(cancellationToken) { - autoUpdater.on('update-downloaded', () => { - global.willAppQuit = true; - autoUpdater.quitAndInstall(); - }); - autoUpdater.downloadUpdate(cancellationToken); + autoUpdater.on('update-downloaded', () => { + global.willAppQuit = true; + autoUpdater.quitAndInstall(); + }); + autoUpdater.downloadUpdate(cancellationToken); } function initialize(appState, mainWindow, notifyOnly = false) { - autoUpdater.autoDownload = false; // To prevent upgrading on quit - const assetsDir = path.resolve(app.getAppPath(), 'assets'); - autoUpdater.on('error', (err) => { - console.error('Error in autoUpdater:', err.message); - }).on('update-available', (info) => { - let cancellationToken = null; - if (isUpdateApplicable(new Date(), appState.skippedVersion, info)) { - updaterModal = createUpdaterModal(mainWindow, { - linuxAppIcon: path.join(assetsDir, 'appicon.png'), - notifyOnly, - }); - updaterModal.on('closed', () => { - updaterModal = null; - }); - updaterModal.on('click-skip', () => { - appState.skippedVersion = info.version; - updaterModal.close(); - }).on('click-remind', () => { - appState.updateCheckedDate = new Date(); - setTimeout(() => { // eslint-disable-line max-nested-callbacks - autoUpdater.checkForUpdates(); + autoUpdater.autoDownload = false; // To prevent upgrading on quit + const assetsDir = path.resolve(app.getAppPath(), 'assets'); + autoUpdater.on('error', (err) => { + log.error('Error in autoUpdater:', err.message); + }).on('update-available', (info) => { + let cancellationToken = null; + if (isUpdateApplicable(new Date(), appState.skippedVersion, info)) { + updaterModal = createUpdaterModal(mainWindow, { + linuxAppIcon: path.join(assetsDir, 'appicon.png'), + notifyOnly, + }); + updaterModal.on('closed', () => { + updaterModal = null; + }); + updaterModal.on('click-skip', () => { + appState.skippedVersion = info.version; + updaterModal.close(); + }).on('click-remind', () => { + appState.updateCheckedDate = new Date(); + setTimeout(() => { // eslint-disable-line max-nested-callbacks + autoUpdater.checkForUpdates(); + }, UPDATER_INTERVAL_IN_MS); + updaterModal.close(); + }).on('click-install', () => { + updaterModal.webContents.send('start-download'); + autoUpdater.signals.progress((data) => { // eslint-disable-line max-nested-callbacks + updaterModal.send('progress', Math.floor(data.percent)); + log.info('progress:', data); + }); + cancellationToken = new CancellationToken(); + downloadAndInstall(cancellationToken); + }).on('click-download', () => { + shell.openExternal('https://about.mattermost.com/download/#mattermostApps'); + }).on('click-release-notes', () => { + shell.openExternal(`https://github.com/mattermost/desktop/releases/v${info.version}`); + }).on('click-cancel', () => { + cancellationToken.cancel(); + updaterModal.close(); + }); + updaterModal.focus(); + } else if (autoUpdater.isManual) { + autoUpdater.emit('update-not-available'); + } + }).on('update-not-available', () => { + if (autoUpdater.isManual) { + dialog.showMessageBox(mainWindow, { + type: 'info', + buttons: ['Close'], + title: 'Your Desktop App is up to date', + message: 'You have the latest version of the Mattermost Desktop App.', + }); + } + setTimeout(() => { + autoUpdater.checkForUpdates(); }, UPDATER_INTERVAL_IN_MS); - updaterModal.close(); - }).on('click-install', () => { - updaterModal.webContents.send('start-download'); - autoUpdater.signals.progress((data) => { // eslint-disable-line max-nested-callbacks - updaterModal.send('progress', Math.floor(data.percent)); - console.log('progress:', data); - }); - cancellationToken = new CancellationToken(); - downloadAndInstall(cancellationToken); - }).on('click-download', () => { - shell.openExternal('https://about.mattermost.com/download/#mattermostApps'); - }).on('click-release-notes', () => { - shell.openExternal(`https://github.com/mattermost/desktop/releases/v${info.version}`); - }).on('click-cancel', () => { - cancellationToken.cancel(); - updaterModal.close(); - }); - updaterModal.focus(); - } else if (autoUpdater.isManual) { - autoUpdater.emit('update-not-available'); - } - }).on('update-not-available', () => { - if (autoUpdater.isManual) { - dialog.showMessageBox(mainWindow, { - type: 'info', - buttons: ['Close'], - title: 'Your Desktop App is up to date', - message: 'You have the latest version of the Mattermost Desktop App.', - }); - } - setTimeout(() => { - autoUpdater.checkForUpdates(); - }, UPDATER_INTERVAL_IN_MS); - }); + }); } function shouldCheckForUpdatesOnStart(updateCheckedDate) { - if (updateCheckedDate) { - if (Date.now() - updateCheckedDate.getTime() < UPDATER_INTERVAL_IN_MS) { - return false; + if (updateCheckedDate) { + if (Date.now() - updateCheckedDate.getTime() < UPDATER_INTERVAL_IN_MS) { + return false; + } } - } - return true; + return true; } function checkForUpdates(isManual = false) { - autoUpdater.isManual = isManual; - if (!updaterModal) { - autoUpdater.checkForUpdates(); - } + autoUpdater.isManual = isManual; + if (!updaterModal) { + autoUpdater.checkForUpdates(); + } } class AutoUpdaterConfig { - constructor() { - this.data = {}; - } + constructor() { + this.data = {}; + } - isNotifyOnly() { - if (process.platform === 'win32') { - return true; + isNotifyOnly() { + if (process.platform === 'win32') { + return true; + } + if (this.data.notifyOnly === true) { + return true; + } + return false; } - if (this.data.notifyOnly === true) { - return true; - } - return false; - } } function loadConfig() { - return new AutoUpdaterConfig(); + return new AutoUpdaterConfig(); } export default { - UPDATER_INTERVAL_IN_MS, - checkForUpdates, - shouldCheckForUpdatesOnStart, - initialize, - loadConfig, + UPDATER_INTERVAL_IN_MS, + checkForUpdates, + shouldCheckForUpdatesOnStart, + initialize, + loadConfig, }; diff --git a/src/main/badge.js b/src/main/badge.js new file mode 100644 index 00000000..346fa9ff --- /dev/null +++ b/src/main/badge.js @@ -0,0 +1,64 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {app} from 'electron'; + +import {UPDATE_BADGE} from 'common/communication'; + +import * as WindowManager from './windows/windowManager'; +import * as AppState from './appState'; + +const MAX_WIN_COUNT = 99; + +function showBadgeWindows(sessionExpired, showUnreadBadge, mentionCount) { + let description = 'You have no unread messages'; + let text; + if (sessionExpired) { + text = '•'; + description = 'Session Expired: Please sign in to continue receiving notifications.'; + } else if (mentionCount > 0) { + text = (mentionCount > MAX_WIN_COUNT) ? `${MAX_WIN_COUNT}+` : mentionCount.toString(); + description = `You have unread mentions (${mentionCount})`; + } else if (showUnreadBadge) { + text = '•'; + description = 'You have unread channels'; + } + WindowManager.setOverlayIcon(text, description, mentionCount > 99); +} + +function showBadgeOSX(sessionExpired, showUnreadBadge, mentionCount) { + let badge = ''; + if (sessionExpired) { + badge = '•'; + } else if (mentionCount > 0) { + badge = mentionCount.toString(); + } else if (showUnreadBadge) { + badge = '•'; + } + app.dock.setBadge(badge); +} + +function showBadgeLinux(sessionExpired, showUnreadBadge, mentionCount) { + if (app.isUnityRunning()) { + const countExpired = sessionExpired ? 1 : 0; + app.setBadgeCount(mentionCount + countExpired); + } +} + +function showBadge(sessionExpired, mentionCount, showUnreadBadge) { + switch (process.platform) { + case 'win32': + showBadgeWindows(sessionExpired, showUnreadBadge, mentionCount); + break; + case 'darwin': + showBadgeOSX(sessionExpired, showUnreadBadge, mentionCount); + break; + case 'linux': + showBadgeLinux(sessionExpired, showUnreadBadge, mentionCount); + break; + } +} + +export function setupBadge() { + AppState.on(UPDATE_BADGE, showBadge); +} diff --git a/src/main/certificateManager.js b/src/main/certificateManager.js new file mode 100644 index 00000000..cd28b95b --- /dev/null +++ b/src/main/certificateManager.js @@ -0,0 +1,60 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import path from 'path'; +import log from 'electron-log'; + +import * as WindowManager from './windows/windowManager'; + +import {addModal} from './views/modalManager'; +import {getLocalURLString} from './utils'; + +const modalPreload = path.resolve(__dirname, '../../dist/modalPreload.js'); +const html = getLocalURLString('certificateModal.html'); + +export class CertificateManager { + constructor() { + this.certificateRequestCallbackMap = new Map(); + } + + handleSelectCertificate = (event, webContents, url, list, callback) => { + if (list.length > 1) { + event.preventDefault(); // prevent the app from getting the first certificate available + + // store callback so it can be called with selected certificate + this.certificateRequestCallbackMap.set(url, callback); + this.popCertificateModal(url, list); + } else { + log.info(`There were ${list.length} candidate certificates. Skipping certificate selection`); + } + } + + popCertificateModal = (url, list) => { + const modalPromise = addModal(`certificate-${url}`, html, modalPreload, {url, list}, WindowManager.getMainWindow()); + modalPromise.then((data) => { + const {cert} = data; + this.handleSelectedCertificate(url, cert); + }).catch((err) => { + if (err) { + log.error('Error processing certificate selection', err); + } + this.handleSelectedCertificate(url); + }); + } + + handleSelectedCertificate = (server, cert) => { + const callback = this.certificateRequestCallbackMap.get(server); + if (!callback) { + log.error(`there was no callback associated with: ${server}`); + return; + } + if (typeof cert === 'undefined') { + log.info('user canceled certificate selection'); + } else { + try { + callback(cert); + } catch (e) { + log.error(`There was a problem using the selected certificate: ${e}`); + } + } + } +} diff --git a/src/main/certificateStore.js b/src/main/certificateStore.js index b0d06f6e..006647fa 100644 --- a/src/main/certificateStore.js +++ b/src/main/certificateStore.js @@ -5,64 +5,64 @@ import fs from 'fs'; -import urlUtils from '../utils/url'; +import urlUtils from 'common/utils/url'; import * as Validator from './Validator'; function comparableCertificate(certificate) { - return { - data: certificate.data.toString(), - issuerName: certificate.issuerName, - }; + return { + data: certificate.data.toString(), + issuerName: certificate.issuerName, + }; } function areEqual(certificate0, certificate1) { - if (certificate0.data !== certificate1.data) { - return false; - } - if (certificate0.issuerName !== certificate1.issuerName) { - return false; - } - return true; + if (certificate0.data !== certificate1.data) { + return false; + } + if (certificate0.issuerName !== certificate1.issuerName) { + return false; + } + return true; } function CertificateStore(storeFile) { - this.storeFile = storeFile; - let storeStr; - try { - storeStr = fs.readFileSync(storeFile, 'utf-8'); - const result = Validator.validateCertificateStore(storeStr); - if (!result) { - throw new Error('Provided certificate store file does not validate, using defaults instead.'); + this.storeFile = storeFile; + let storeStr; + try { + storeStr = fs.readFileSync(storeFile, 'utf-8'); + const result = Validator.validateCertificateStore(storeStr); + if (!result) { + throw new Error('Provided certificate store file does not validate, using defaults instead.'); + } + this.data = result; + } catch (e) { + this.data = {}; } - this.data = result; - } catch (e) { - this.data = {}; - } } CertificateStore.prototype.save = function save() { - fs.writeFileSync(this.storeFile, JSON.stringify(this.data, null, ' ')); + fs.writeFileSync(this.storeFile, JSON.stringify(this.data, null, ' ')); }; CertificateStore.prototype.add = function add(targetURL, certificate) { - this.data[urlUtils.getHost(targetURL)] = comparableCertificate(certificate); + this.data[urlUtils.getHost(targetURL)] = comparableCertificate(certificate); }; CertificateStore.prototype.isExisting = function isExisting(targetURL) { - return this.data.hasOwnProperty(urlUtils.getHost(targetURL)); + return Object.prototype.hasOwnProperty.call(this.data, urlUtils.getHost(targetURL)); }; CertificateStore.prototype.isTrusted = function isTrusted(targetURL, certificate) { - const host = urlUtils.getHost(targetURL); - if (!this.isExisting(targetURL)) { - return false; - } - return areEqual(this.data[host], comparableCertificate(certificate)); + const host = urlUtils.getHost(targetURL); + if (!this.isExisting(targetURL)) { + return false; + } + return areEqual(this.data[host], comparableCertificate(certificate)); }; export default { - load(storeFile) { - return new CertificateStore(storeFile); - }, + load(storeFile) { + return new CertificateStore(storeFile); + }, }; diff --git a/src/main/contextMenu.js b/src/main/contextMenu.js new file mode 100644 index 00000000..93d569e4 --- /dev/null +++ b/src/main/contextMenu.js @@ -0,0 +1,64 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import electronContextMenu from 'electron-context-menu'; + +import urlUtils from 'common/utils/url'; + +let disposeCurrent; +let menuOptions = { + shouldShowMenu: (e, p) => { + const isInternalLink = p.linkURL.endsWith('#') && p.linkURL.slice(0, -1) === p.pageURL; + let isInternalSrc; + try { + const srcurl = urlUtils.parseURL(p.srcURL); + isInternalSrc = srcurl.protocol === 'file:'; + } catch (err) { + isInternalSrc = false; + } + return p.isEditable || (p.mediaType !== 'none' && !isInternalSrc) || (p.linkURL !== '' && !isInternalLink) || p.misspelledWord !== '' || p.selectionText !== ''; + }, + showLookUpSelection: true, + showSearchWithGoogle: true, + showCopyImage: true, + showSaveImage: true, + showSaveImageAs: true, + showServices: true, +}; + +function dispose() { + if (disposeCurrent) { + disposeCurrent(); + disposeCurrent = null; + } +} + +function saveOptions(options) { + const providedOptions = options || {}; + + menuOptions = Object.assign({}, menuOptions, providedOptions); +} + +function reload(target) { + dispose(); + + /** + * Work-around issue with passing `WebContents` to `electron-context-menu` in Electron 11 + * @see https://github.com/sindresorhus/electron-context-menu/issues/123 + */ + const options = target ? {window: {webContents: target, inspectElement: target.inspectElement.bind(target), isDestroyed: target.isDestroyed.bind(target), off: target.off.bind(target)}, ...menuOptions} : menuOptions; + disposeCurrent = electronContextMenu(options); +} + +function setup(options) { + saveOptions(options); + dispose(); + disposeCurrent = electronContextMenu(menuOptions); +} + +export default { + setup, + dispose, + reload, +}; diff --git a/src/main/cookieManager.js b/src/main/cookieManager.js index 38a827d9..a34962d2 100644 --- a/src/main/cookieManager.js +++ b/src/main/cookieManager.js @@ -2,22 +2,23 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {app} from 'electron'; +import log from 'electron-log'; function flushCookiesStore(session) { - session.cookies.flushStore().catch((err) => { - console.log(`There was a problem flushing cookies:\n${err}`); - }); + session.cookies.flushStore().catch((err) => { + log.error(`There was a problem flushing cookies:\n${err}`); + }); } export default function initCookieManager(session) { - // Somehow cookies are not immediately saved to disk. - // So manually flush cookie store to disk on closing the app. - // https://github.com/electron/electron/issues/8416 - app.on('before-quit', () => { - flushCookiesStore(session); - }); + // Somehow cookies are not immediately saved to disk. + // So manually flush cookie store to disk on closing the app. + // https://github.com/electron/electron/issues/8416 + app.on('before-quit', () => { + flushCookiesStore(session); + }); - app.on('browser-window-blur', () => { - flushCookiesStore(session); - }); + app.on('browser-window-blur', () => { + flushCookiesStore(session); + }); } diff --git a/src/main/downloadURL.js b/src/main/downloadURL.js index 206689cb..0ebdec91 100644 --- a/src/main/downloadURL.js +++ b/src/main/downloadURL.js @@ -1,61 +1,65 @@ // Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. + import fs from 'fs'; + import path from 'path'; import zlib from 'zlib'; import electron from 'electron'; const {app, dialog} = electron; -export default function downloadURL(browserWindow, URL, callback) { - const {net} = electron; - const request = net.request(URL); - request.setHeader('Accept-Encoding', 'gzip,deflate'); - request.on('response', (response) => { - const file = getAttachmentName(response.headers); - const dialogOptions = { - defaultPath: path.join(app.getPath('downloads'), file), - }; - dialog.showSaveDialog( - browserWindow, - dialogOptions - ).then( - (filename) => { - if (filename) { - saveResponseBody(response, filename, callback); - } - } - ).catch((err) => { - callback(err); - }); - }).on('error', callback); - request.end(); +import * as WindowManager from './windows/windowManager'; + +export default function downloadURL(URL, callback) { + const {net} = electron; + const request = net.request(URL); + request.setHeader('Accept-Encoding', 'gzip,deflate'); + request.on('response', (response) => { + const file = getAttachmentName(response.headers); + const dialogOptions = { + defaultPath: path.join(app.getPath('downloads'), file), + }; + dialog.showSaveDialog( + WindowManager.getMainWindow(true), + dialogOptions, + ).then( + (filename) => { + if (filename) { + saveResponseBody(response, filename, callback); + } + }, + ).catch((err) => { + callback(err); + }); + }).on('error', callback); + request.end(); } function getAttachmentName(headers) { - if (headers['content-disposition']) { - const contentDisposition = headers['content-disposition'][0]; - const matched = contentDisposition.match(/filename="(.*)"/); - if (matched) { - return path.basename(matched[1]); + if (headers['content-disposition']) { + const contentDisposition = headers['content-disposition'][0]; + const matched = contentDisposition.match(/filename="(.*)"/); + if (matched) { + return path.basename(matched[1]); + } } - } - return ''; + return ''; } function saveResponseBody(response, filename, callback) { - const output = fs.createWriteStream(filename); - output.on('close', callback); - switch (response.headers['content-encoding']) { - case 'gzip': - response.pipe(zlib.createGunzip()).pipe(output).on('error', callback); - break; - case 'deflate': - response.pipe(zlib.createInflate()).pipe(output).on('error', callback); - break; - default: - response.pipe(output).on('error', callback); - break; - } + const output = fs.createWriteStream(filename); + output.on('close', callback); + switch (response.headers['content-encoding']) { + case 'gzip': + response.pipe(zlib.createGunzip()).pipe(output).on('error', callback); + break; + case 'deflate': + response.pipe(zlib.createInflate()).pipe(output).on('error', callback); + break; + default: + response.pipe(output).on('error', callback); + break; + } } diff --git a/src/main/main.js b/src/main/main.js new file mode 100644 index 00000000..dd341b44 --- /dev/null +++ b/src/main/main.js @@ -0,0 +1,705 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai +import fs from 'fs'; + +import path from 'path'; + +import electron from 'electron'; +import isDev from 'electron-is-dev'; +import installExtension, {REACT_DEVELOPER_TOOLS} from 'electron-devtools-installer'; +import log from 'electron-log'; +import 'airbnb-js-shims/target/es2015'; + +import Utils from 'common/utils/util'; +import urlUtils from 'common/utils/url'; + +import { + SWITCH_SERVER, + FOCUS_BROWSERVIEW, + QUIT, + DARK_MODE_CHANGE, + DOUBLE_CLICK_ON_WINDOW, + SHOW_NEW_SERVER_MODAL, + WINDOW_CLOSE, + WINDOW_MAXIMIZE, + WINDOW_MINIMIZE, + WINDOW_RESTORE, + NOTIFY_MENTION, + GET_DOWNLOAD_LOCATION, + SHOW_SETTINGS_WINDOW, + RELOAD_CONFIGURATION, + USER_ACTIVITY_UPDATE, +} from 'common/communication'; +import Config from 'common/config'; + +import {protocols} from '../../electron-builder.json'; + +import AutoLauncher from './AutoLauncher'; +import CriticalErrorHandler from './CriticalErrorHandler'; +import upgradeAutoLaunch from './autoLaunch'; +import CertificateStore from './certificateStore'; +import TrustedOriginsStore from './trustedOrigins'; +import appMenu from './menus/app'; +import trayMenu from './menus/tray'; +import allowProtocolDialog from './allowProtocolDialog'; +import AppVersionManager from './AppVersionManager'; +import initCookieManager from './cookieManager'; +import UserActivityMonitor from './UserActivityMonitor'; +import * as WindowManager from './windows/windowManager'; +import {displayMention, displayDownloadCompleted} from './notifications'; + +import parseArgs from './ParseArgs'; +import {addModal} from './views/modalManager'; +import {getLocalURLString, getLocalPreload} from './utils'; +import {destroyTray, refreshTrayImages, setTrayMenu, setupTray} from './tray/tray'; +import {AuthManager} from './authManager'; +import {CertificateManager} from './certificateManager'; +import {setupBadge} from './badge'; + +if (process.env.NODE_ENV !== 'production' && module.hot) { + module.hot.accept(); +} + +// pull out required electron components like this +// as not all components can be referenced before the app is ready +const { + app, + Menu, + ipcMain, + dialog, + session, +} = electron; +const criticalErrorHandler = new CriticalErrorHandler(); +const userActivityMonitor = new UserActivityMonitor(); +const certificateErrorCallbacks = new Map(); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let certificateStore = null; +let trustedOriginsStore = null; +let scheme = null; +let appVersion = null; +let config = null; +let authManager = null; +let certificateManager = null; + +/** + * Main entry point for the application, ensures that everything initializes in the proper order + */ +async function initialize() { + process.on('uncaughtException', criticalErrorHandler.processUncaughtExceptionHandler.bind(criticalErrorHandler)); + global.willAppQuit = false; + + // initialization that can run before the app is ready + initializeArgs(); + await initializeConfig(); + initializeAppEventListeners(); + initializeBeforeAppReady(); + + // wait for registry config data to load and app ready event + await Promise.all([ + app.whenReady(), + ]); + + // no need to continue initializing if app is quitting + if (global.willAppQuit) { + return; + } + + // initialization that should run once the app is ready + initializeInterCommunicationEventListeners(); + initializeAfterAppReady(); +} + +// attempt to initialize the application +try { + initialize(); +} catch (error) { + throw new Error(`App initialization failed: ${error.toString()}`); +} + +// +// initialization sub functions +// + +function initializeArgs() { + global.args = parseArgs(process.argv.slice(1)); + + // output the application version via cli when requested (-v or --version) + if (global.args.version) { + process.stdout.write(`v.${app.getVersion()}\n`); + process.exit(0); // eslint-disable-line no-process-exit + } + + global.isDev = isDev && !global.args.disableDevMode; // this doesn't seem to be right and isn't used as the single source of truth + + if (global.args.dataDir) { + app.setPath('userData', path.resolve(global.args.dataDir)); + } +} + +async function initializeConfig() { + const loadConfig = new Promise((resolve) => { + config = new Config(app.getPath('userData') + '/config.json'); + config.once('update', (configData) => { + config.on('update', handleConfigUpdate); + config.on('synchronize', handleConfigSynchronize); + config.on('darkModeChange', handleDarkModeChange); + handleConfigUpdate(configData); + resolve(); + }); + config.init(); + }); + + return loadConfig; +} + +function initializeAppEventListeners() { + app.on('second-instance', handleAppSecondInstance); + app.on('window-all-closed', handleAppWindowAllClosed); + app.on('browser-window-created', handleAppBrowserWindowCreated); + app.on('activate', handleAppActivate); + app.on('before-quit', handleAppBeforeQuit); + app.on('certificate-error', handleAppCertificateError); + app.on('select-client-certificate', handleSelectCertificate); + app.on('gpu-process-crashed', handleAppGPUProcessCrashed); + app.on('login', handleAppLogin); + app.on('will-finish-launching', handleAppWillFinishLaunching); +} + +function initializeBeforeAppReady() { + certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json')); + trustedOriginsStore = new TrustedOriginsStore(path.resolve(app.getPath('userData'), 'trustedOrigins.json')); + trustedOriginsStore.load(); + + // prevent using a different working directory, which happens on windows running after installation. + const expectedPath = path.dirname(process.execPath); + if (process.cwd() !== expectedPath && !isDev) { + log.warn(`Current working directory is ${process.cwd()}, changing into ${expectedPath}`); + process.chdir(expectedPath); + } + + // can only call this before the app is ready + if (config.enableHardwareAcceleration === false) { + app.disableHardwareAcceleration(); + } + + refreshTrayImages(config.trayIconTheme); + + // If there is already an instance, quit this one + const gotTheLock = app.requestSingleInstanceLock(); + if (!gotTheLock) { + app.exit(); + global.willAppQuit = true; + } + + allowProtocolDialog.init(); + + authManager = new AuthManager(config, trustedOriginsStore); + certificateManager = new CertificateManager(); + + if (isDev) { + log.info('In development mode, deeplinking is disabled'); + } else if (protocols && protocols[0] && protocols[0].schemes && protocols[0].schemes[0]) { + scheme = protocols[0].schemes[0]; + app.setAsDefaultProtocolClient(scheme); + } +} + +function initializeInterCommunicationEventListeners() { + ipcMain.on(RELOAD_CONFIGURATION, handleReloadConfig); + ipcMain.on(NOTIFY_MENTION, handleMentionNotification); + ipcMain.handle('get-app-version', handleAppVersion); + ipcMain.on('update-menu', handleUpdateMenuEvent); + ipcMain.on(FOCUS_BROWSERVIEW, WindowManager.focusBrowserView); + + if (process.platform !== 'darwin') { + ipcMain.on('open-app-menu', handleOpenAppMenu); + } + + ipcMain.on(SWITCH_SERVER, handleSwitchServer); + + ipcMain.on(QUIT, handleQuit); + + ipcMain.on(DOUBLE_CLICK_ON_WINDOW, WindowManager.handleDoubleClick); + + ipcMain.on(SHOW_NEW_SERVER_MODAL, handleNewServerModal); + ipcMain.on(WINDOW_CLOSE, WindowManager.close); + ipcMain.on(WINDOW_MAXIMIZE, WindowManager.maximize); + ipcMain.on(WINDOW_MINIMIZE, WindowManager.minimize); + ipcMain.on(WINDOW_RESTORE, WindowManager.restore); + ipcMain.on(SHOW_SETTINGS_WINDOW, WindowManager.showSettingsWindow); + ipcMain.handle(GET_DOWNLOAD_LOCATION, handleSelectDownload); +} + +// +// config event handlers +// + +function handleConfigUpdate(newConfig) { + if (process.platform === 'win32' || process.platform === 'linux') { + const appLauncher = new AutoLauncher(); + const autoStartTask = config.autostart ? appLauncher.enable() : appLauncher.disable(); + autoStartTask.then(() => { + log.info('config.autostart has been configured:', newConfig.autostart); + }).catch((err) => { + log.error('error:', err); + }); + WindowManager.setConfig(newConfig.data); + } + + ipcMain.emit('update-menu', true, config); +} + +function handleConfigSynchronize() { + // TODO: send this to server manager + WindowManager.setConfig(config.data); + if (app.isReady()) { + WindowManager.sendToRenderer(RELOAD_CONFIGURATION); + } +} + +function handleReloadConfig() { + config.reload(); + WindowManager.setConfig(config.data); +} + +function handleAppVersion() { + return { + name: app.getName(), + version: app.getVersion(), + }; +} + +function handleDarkModeChange(darkMode) { + refreshTrayImages(config.trayIconTheme); + WindowManager.sendToRenderer(DARK_MODE_CHANGE, darkMode); + WindowManager.updateLoadingScreenDarkMode(darkMode); +} + +// +// app event handlers +// + +// activate first app instance, subsequent instances will quit themselves +function handleAppSecondInstance(event, argv) { + // Protocol handler for win32 + // argv: An array of the second instance’s (command line / deep linked) arguments + const deeplinkingUrl = getDeeplinkingURL(argv); + openDeepLink(deeplinkingUrl); +} + +function handleAppWindowAllClosed() { + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit(); + } +} + +function handleAppBrowserWindowCreated(error, newWindow) { + // Screen cannot be required before app is ready + resizeScreen(electron.screen, newWindow); +} + +function handleAppActivate() { + WindowManager.showMainWindow(); +} + +function handleAppBeforeQuit() { + // Make sure tray icon gets removed if the user exits via CTRL-Q + destroyTray(); + global.willAppQuit = true; +} + +function handleQuit(e, reason, stack) { + log.error(`Exiting App. Reason: ${reason}`); + log.info(`Stacktrace:\n${stack}`); + handleAppBeforeQuit(); + app.quit(); +} + +function handleSelectCertificate(event, webContents, url, list, callback) { + certificateManager.handleSelectCertificate(event, webContents, url, list, callback); +} + +function handleAppCertificateError(event, webContents, url, error, certificate, callback) { + const parsedURL = new URL(url); + if (!parsedURL) { + return; + } + const origin = parsedURL.origin; + if (certificateStore.isTrusted(origin, certificate)) { + event.preventDefault(); + callback(true); + } else { + // update the callback + const errorID = `${origin}:${error}`; + + // if we are already showing that error, don't add more dialogs + if (certificateErrorCallbacks.has(errorID)) { + log.warn(`Ignoring already shown dialog for ${errorID}`); + certificateErrorCallbacks.set(errorID, callback); + return; + } + const extraDetail = certificateStore.isExisting(origin) ? 'Certificate is different from previous one.\n\n' : ''; + const detail = `${extraDetail}origin: ${origin}\nError: ${error}`; + + certificateErrorCallbacks.set(errorID, callback); + + // TODO: should we move this to window manager or provide a handler for dialogs? + const mainWindow = WindowManager.getMainWindow(); + dialog.showMessageBox(mainWindow, { + title: 'Certificate Error', + message: 'There is a configuration issue with this Mattermost server, or someone is trying to intercept your connection. You also may need to sign into the Wi-Fi you are connected to using your web browser.', + type: 'error', + detail, + buttons: ['More Details', 'Cancel Connection'], + cancelId: 1, + }).then( + ({response}) => { + if (response === 0) { + return dialog.showMessageBox(mainWindow, { + title: 'Certificate Not Trusted', + message: `Certificate from "${certificate.issuerName}" is not trusted.`, + detail: extraDetail, + type: 'error', + buttons: ['Trust Insecure Certificate', 'Cancel Connection'], + cancelId: 1, + }); + } + return {response}; + }).then( + ({response: responseTwo}) => { + if (responseTwo === 0) { + certificateStore.add(origin, certificate); + certificateStore.save(); + certificateErrorCallbacks.get(errorID)(true); + certificateErrorCallbacks.delete(errorID); + webContents.loadURL(url); + } else { + certificateErrorCallbacks.get(errorID)(false); + certificateErrorCallbacks.delete(errorID); + } + }).catch( + (dialogError) => { + log.error(`There was an error with the Certificate Error dialog: ${dialogError}`); + certificateErrorCallbacks.delete(errorID); + }); + } +} + +function handleAppLogin(event, webContents, request, authInfo, callback) { + authManager.handleAppLogin(event, webContents, request, authInfo, callback); +} + +function handleAppGPUProcessCrashed(event, killed) { + log.error(`The GPU process has crashed (killed = ${killed})`); +} + +function openDeepLink(deeplinkingUrl) { + try { + WindowManager.showMainWindow(deeplinkingUrl); + } catch (err) { + log.error(`There was an error opening the deeplinking url: ${err}`); + } +} + +function handleAppWillFinishLaunching() { + // Protocol handler for osx + app.on('open-url', (event, url) => { + log.info(`Handling deeplinking url: ${url}`); + event.preventDefault(); + const deeplinkingUrl = getDeeplinkingURL([url]); + if (deeplinkingUrl) { + if (app.isReady() && deeplinkingUrl) { + openDeepLink(deeplinkingUrl); + } else { + app.once('ready', () => openDeepLink(deeplinkingUrl)); + } + } + }); +} + +function handleSwitchServer(event, serverName) { + WindowManager.switchServer(serverName); +} + +function handleNewServerModal() { + const html = getLocalURLString('newServer.html'); + + const modalPreload = getLocalPreload('modalPreload.js'); + + const modalPromise = addModal('newServer', html, modalPreload, {}, WindowManager.getMainWindow()); + if (modalPromise) { + modalPromise.then((data) => { + const teams = config.teams; + const order = teams.length; + teams.push({...data, order}); + config.set('teams', teams); + }).catch((e) => { + // e is undefined for user cancellation + if (e) { + log.error(`there was an error in the new server modal: ${e}`); + } + }); + } else { + log.warn('There is already a new server modal'); + } +} + +function initializeAfterAppReady() { + app.setAppUserModelId('Mattermost.Desktop'); // Use explicit AppUserModelID + + const appVersionJson = path.join(app.getPath('userData'), 'app-state.json'); + appVersion = new AppVersionManager(appVersionJson); + if (wasUpdated(appVersion.lastAppVersion)) { + clearAppCache(); + } + appVersion.lastAppVersion = app.getVersion(); + + if (!global.isDev) { + upgradeAutoLaunch(); + } + + if (global.isDev) { + installExtension(REACT_DEVELOPER_TOOLS). + then((name) => log.info(`Added Extension: ${name}`)). + catch((err) => log.error('An error occurred: ', err)); + } + + // Workaround for MM-22193 + // From this post: https://github.com/electron/electron/issues/19468#issuecomment-549593139 + // Electron 6 has a bug that affects users on Windows 10 using dark mode, causing the app to hang + // This workaround deletes a file that stops that from happening + if (process.platform === 'win32') { + const appUserDataPath = app.getPath('userData'); + const devToolsExtensionsPath = path.join(appUserDataPath, 'DevTools Extensions'); + try { + fs.unlinkSync(devToolsExtensionsPath); + } catch (_) { + // don't complain if the file doesn't exist + } + } + + let deeplinkingURL; + + // Protocol handler for win32 + if (process.platform === 'win32') { + const args = process.argv.slice(1); + if (Array.isArray(args) && args.length > 0) { + deeplinkingURL = getDeeplinkingURL(args); + } + } + + initCookieManager(session.defaultSession); + + WindowManager.showMainWindow(deeplinkingURL); + + if (config.teams.length === 0) { + WindowManager.showSettingsWindow(); + } + + criticalErrorHandler.setMainWindow(WindowManager.getMainWindow()); + + // listen for status updates and pass on to renderer + userActivityMonitor.on('status', (status) => { + WindowManager.sendToMattermostViews(USER_ACTIVITY_UPDATE, status); + }); + + // start monitoring user activity (needs to be started after the app is ready) + userActivityMonitor.startMonitoring(); + + if (shouldShowTrayIcon()) { + setupTray(config.trayIconTheme); + } + setupBadge(); + + session.defaultSession.on('will-download', (event, item, webContents) => { + const filename = item.getFilename(); + const fileElements = filename.split('.'); + const filters = []; + if (fileElements.length > 1) { + filters.push({ + name: 'All files', + extensions: ['*'], + }); + } + item.setSaveDialogOptions({ + title: filename, + defaultPath: path.resolve(config.combinedData.downloadLocation, filename), + filters, + }); + + item.on('done', (doneEvent, state) => { + if (state === 'completed') { + displayDownloadCompleted(filename, item.savePath, urlUtils.getServer(webContents.getURL(), config.teams)); + } + }); + }); + + ipcMain.emit('update-menu', true, config); + + ipcMain.emit('update-dict'); + + // supported permission types + const supportedPermissionTypes = [ + 'media', + 'geolocation', + 'notifications', + 'fullscreen', + 'openExternal', + ]; + + // handle permission requests + // - approve if a supported permission type and the request comes from the renderer or one of the defined servers + session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { + // is the requested permission type supported? + if (!supportedPermissionTypes.includes(permission)) { + callback(false); + return; + } + + // is the request coming from the renderer? + const mainWindow = WindowManager.getMainWindow(); + if (mainWindow && webContents.id === mainWindow.webContents.id) { + callback(true); + return; + } + + const requestingURL = webContents.getURL(); + + // is the requesting url trusted? + callback(urlUtils.isTrustedURL(requestingURL, config.teams)); + }); +} + +// +// ipc communication event handlers +// + +function handleMentionNotification(event, title, body, channel, teamId, silent, data) { + displayMention(title, body, channel, teamId, silent, event.sender, data); +} + +function handleOpenAppMenu() { + const windowMenu = Menu.getApplicationMenu(); + if (!windowMenu) { + log.error('No application menu found'); + return; + } + windowMenu.popup({ + window: WindowManager.getMainWindow(), + x: 18, + y: 18, + }); +} + +function handleCloseAppMenu() { + WindowManager.focusBrowserView(); +} + +function handleUpdateMenuEvent(event, menuConfig) { + // TODO: this might make sense to move to window manager? so it updates the window referenced if needed. + const mainWindow = WindowManager.getMainWindow(); + const aMenu = appMenu.createMenu(menuConfig); + Menu.setApplicationMenu(aMenu); + aMenu.addListener('menu-will-close', handleCloseAppMenu); + + // set up context menu for tray icon + if (shouldShowTrayIcon()) { + const tMenu = trayMenu.createMenu(menuConfig.data); + setTrayMenu(tMenu, mainWindow); + } +} + +async function handleSelectDownload(event, startFrom) { + const message = 'Specify the folder where files will download'; + const result = await dialog.showOpenDialog({defaultPath: startFrom, + message, + properties: + ['openDirectory', 'createDirectory', 'dontAddToRecent', 'promptToCreate']}); + return result.filePaths[0]; +} + +// +// helper functions +// + +function getDeeplinkingURL(args) { + if (Array.isArray(args) && args.length) { + // deeplink urls should always be the last argument, but may not be the first (i.e. Windows with the app already running) + const url = args[args.length - 1]; + if (url && scheme && url.startsWith(scheme) && urlUtils.isValidURI(url)) { + return url; + } + } + return null; +} + +function shouldShowTrayIcon() { + return config.showTrayIcon || process.platform === 'win32'; +} + +function wasUpdated(lastAppVersion) { + return lastAppVersion !== app.getVersion(); +} + +function clearAppCache() { + // TODO: clear cache on browserviews, not in the renderer. + const mainWindow = WindowManager.getMainWindow(); + if (mainWindow) { + mainWindow.webContents.session.clearCache().then(mainWindow.reload); + } else { + //Wait for mainWindow + setTimeout(clearAppCache, 100); + } +} + +function isWithinDisplay(state, display) { + const startsWithinDisplay = !(state.x > display.maxX || state.y > display.maxY || state.x < display.minX || state.y < display.minY); + if (!startsWithinDisplay) { + return false; + } + + // is half the screen within the display? + const midX = state.x + (state.width / 2); + const midY = state.y + (state.height / 2); + return !(midX > display.maxX || midY > display.maxY); +} + +function getValidWindowPosition(state) { + // Check if the previous position is out of the viewable area + // (e.g. because the screen has been plugged off) + const boundaries = Utils.getDisplayBoundaries(); + const display = boundaries.find((boundary) => { + return isWithinDisplay(state, boundary); + }); + + if (typeof display === 'undefined') { + return {}; + } + return {x: state.x, y: state.y}; +} + +function resizeScreen(screen, browserWindow) { + function handle() { + const position = browserWindow.getPosition(); + const size = browserWindow.getSize(); + const validPosition = getValidWindowPosition({ + x: position[0], + y: position[1], + width: size[0], + height: size[1], + }); + if (typeof validPosition.x !== 'undefined' || typeof validPosition.y !== 'undefined') { + browserWindow.setPosition(validPosition.x || 0, validPosition.y || 0); + } else { + browserWindow.center(); + } + } + + browserWindow.on('restore', handle); + handle(); +} diff --git a/src/main/mainWindow.js b/src/main/mainWindow.js deleted file mode 100644 index cb483a14..00000000 --- a/src/main/mainWindow.js +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import fs from 'fs'; -import path from 'path'; -import os from 'os'; - -import {app, BrowserWindow} from 'electron'; - -import * as Validator from './Validator'; - -function saveWindowState(file, window) { - const windowState = window.getBounds(); - windowState.maximized = window.isMaximized(); - try { - fs.writeFileSync(file, JSON.stringify(windowState)); - } catch (e) { - // [Linux] error happens only when the window state is changed before the config dir is created. - console.log(e); - } -} - -function isFramelessWindow() { - return os.platform() === 'darwin' || (os.platform() === 'win32' && os.release().startsWith('10')); -} - -function createMainWindow(config, options) { - const defaultWindowWidth = 1000; - const defaultWindowHeight = 700; - const minimumWindowWidth = 400; - const minimumWindowHeight = 240; - - // Create the browser window. - const boundsInfoPath = path.join(app.getPath('userData'), 'bounds-info.json'); - let windowOptions; - try { - windowOptions = JSON.parse(fs.readFileSync(boundsInfoPath, 'utf-8')); - windowOptions = Validator.validateBoundsInfo(windowOptions); - if (!windowOptions) { - throw new Error('Provided bounds info file does not validate, using defaults instead.'); - } - } catch (e) { - // Follow Electron's defaults, except for window dimensions which targets 1024x768 screen resolution. - windowOptions = {width: defaultWindowWidth, height: defaultWindowHeight}; - } - - const {maximized: windowIsMaximized} = windowOptions; - - if (process.platform === 'linux') { - windowOptions.icon = options.linuxAppIcon; - } - Object.assign(windowOptions, { - title: app.name, - fullscreenable: true, - show: false, // don't start the window until it is ready and only if it isn't hidden - paintWhenInitiallyHidden: true, // we want it to start painting to get info from the webapp - minWidth: minimumWindowWidth, - minHeight: minimumWindowHeight, - frame: !isFramelessWindow(), - fullscreen: false, - titleBarStyle: 'hiddenInset', - backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - webviewTag: true, - disableBlinkFeatures: 'Auxclick', - }, - }); - - const mainWindow = new BrowserWindow(windowOptions); - mainWindow.deeplinkingUrl = options.deeplinkingUrl; - mainWindow.setMenuBarVisibility(false); - - const indexURL = global.isDev ? 'http://localhost:8080/browser/index.html' : `file://${app.getAppPath()}/browser/index.html`; - mainWindow.loadURL(indexURL); - - mainWindow.once('ready-to-show', () => { - mainWindow.webContents.zoomLevel = 0; - - // handle showing the window when not launched by auto-start - // - when not configured to auto-start, immediately show contents and optionally maximize as needed - mainWindow.show(); - if (windowIsMaximized) { - mainWindow.maximize(); - } - }); - - mainWindow.once('show', () => { - // handle showing the app when hidden to the tray icon by auto-start - // - optionally maximize the window as needed - if (windowIsMaximized) { - mainWindow.maximize(); - } - }); - - mainWindow.once('restore', () => { - // handle restoring the window when minimized to the app icon by auto-start - // - optionally maximize the window as needed - if (windowIsMaximized) { - mainWindow.maximize(); - } - }); - - mainWindow.webContents.on('will-attach-webview', (event, webPreferences) => { - webPreferences.nodeIntegration = false; - webPreferences.contextIsolation = true; - }); - - // App should save bounds when a window is closed. - // However, 'close' is not fired in some situations(shutdown, ctrl+c) - // because main process is killed in such situations. - // 'blur' event was effective in order to avoid this. - // Ideally, app should detect that OS is shutting down. - mainWindow.on('blur', () => { - saveWindowState(boundsInfoPath, mainWindow); - mainWindow.blurWebView(); - }); - - mainWindow.on('close', (event) => { - if (global.willAppQuit) { // when [Ctrl|Cmd]+Q - saveWindowState(boundsInfoPath, mainWindow); - } else { // Minimize or hide the window for close button. - event.preventDefault(); - function hideWindow(window) { - window.blur(); // To move focus to the next top-level window in Windows - window.hide(); - } - switch (process.platform) { - case 'win32': - hideWindow(mainWindow); - break; - case 'linux': - if (config.minimizeToTray) { - hideWindow(mainWindow); - } else { - mainWindow.minimize(); - } - break; - case 'darwin': - // need to leave fullscreen first, then hide the window - if (mainWindow.isFullScreen()) { - mainWindow.once('leave-full-screen', () => { - app.hide(); - }); - mainWindow.setFullScreen(false); - } else { - app.hide(); - } - break; - default: - } - } - }); - - // Register keyboard shortcuts - mainWindow.webContents.on('before-input-event', (event, input) => { - // Add Alt+Cmd+(Right|Left) as alternative to switch between servers - if (process.platform === 'darwin') { - if (input.alt && input.meta) { - if (input.key === 'ArrowRight') { - mainWindow.webContents.send('select-next-tab'); - } - if (input.key === 'ArrowLeft') { - mainWindow.webContents.send('select-previous-tab'); - } - } - } - }); - - return mainWindow; -} - -export default createMainWindow; diff --git a/src/main/menus/app.js b/src/main/menus/app.js index 6b01cb21..65f3f1fa 100644 --- a/src/main/menus/app.js +++ b/src/main/menus/app.js @@ -3,291 +3,256 @@ // See LICENSE.txt for license information. 'use strict'; -import {app, dialog, Menu, shell} from 'electron'; +import {app, Menu, session, shell, webContents} from 'electron'; -function createTemplate(mainWindow, config, isDev) { - const settingsURL = isDev ? 'http://localhost:8080/browser/settings.html' : `file://${app.getAppPath()}/browser/settings.html`; +import {ADD_SERVER, SELECT_NEXT_TAB, SELECT_PREVIOUS_TAB} from 'common/communication'; - const separatorItem = { - type: 'separator', - }; +import * as WindowManager from '../windows/windowManager'; - const appName = app.name; - const firstMenuName = (process.platform === 'darwin') ? appName : 'File'; - const template = []; +function createTemplate(config) { + const separatorItem = { + type: 'separator', + }; - let platformAppMenu = process.platform === 'darwin' ? [{ - label: 'About ' + appName, - role: 'about', - click() { - dialog.showMessageBox(mainWindow, { - buttons: ['OK'], - message: `${appName} Desktop ${app.getVersion()}`, - }); - }, - }, separatorItem, { - label: 'Preferences...', - accelerator: 'CmdOrCtrl+,', - click() { - mainWindow.loadURL(settingsURL); - }, - }] : [{ - label: 'Settings...', - accelerator: 'CmdOrCtrl+,', - click() { - mainWindow.loadURL(settingsURL); - }, - }]; + const isMac = process.platform === 'darwin'; + const appName = app.name; + const firstMenuName = isMac ? appName : 'File'; + const template = []; - if (config.enableServerManagement === true) { + const settingsLabel = isMac ? 'Preferences...' : 'Settings...'; + + let platformAppMenu = []; + if (isMac) { + platformAppMenu.push( + { + label: 'About ' + appName, + role: 'about', + }, + ); + platformAppMenu.push(separatorItem); + } platformAppMenu.push({ - label: 'Sign in to Another Server', - click() { - mainWindow.webContents.send('add-server'); - }, - }); - } - - platformAppMenu = platformAppMenu.concat(process.platform === 'darwin' ? [ - separatorItem, { - role: 'hide', - }, { - role: 'hideothers', - }, { - role: 'unhide', - }, separatorItem, { - role: 'quit', - }] : [ - separatorItem, { - role: 'quit', - accelerator: 'CmdOrCtrl+Q', - click() { - app.quit(); - }, - }] - ); - - template.push({ - label: '&' + firstMenuName, - submenu: [ - ...platformAppMenu, - ], - }); - template.push({ - label: '&Edit', - submenu: [{ - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - click() { - mainWindow.webContents.send('undo'); - }, - }, { - label: 'Redo', - accelerator: 'CmdOrCtrl+SHIFT+Z', - click() { - mainWindow.webContents.send('redo'); - }, - }, separatorItem, { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - click() { - mainWindow.webContents.send('cut'); - }, - }, { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - click() { - mainWindow.webContents.send('copy'); - }, - }, { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - click() { - mainWindow.webContents.send('paste'); - }, - }, { - label: 'Paste and Match Style', - accelerator: 'CmdOrCtrl+SHIFT+V', - visible: process.platform === 'darwin', - click() { - mainWindow.webContents.send('paste-and-match'); - }, - }, { - role: 'selectall', - accelerator: 'CmdOrCtrl+A', - }], - }); - - const viewSubMenu = [{ - label: 'Find..', - accelerator: 'CmdOrCtrl+F', - click(item, focusedWindow) { - focusedWindow.webContents.send('toggle-find'); - }, - }, { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click(item, focusedWindow) { - if (focusedWindow) { - if (focusedWindow === mainWindow) { - mainWindow.webContents.send('reload-tab'); - } else { - focusedWindow.reload(); - } - } - }, - }, { - label: 'Clear Cache and Reload', - accelerator: 'Shift+CmdOrCtrl+R', - click(item, focusedWindow) { - if (focusedWindow) { - if (focusedWindow === mainWindow) { - mainWindow.webContents.send('clear-cache-and-reload-tab'); - } else { - focusedWindow.webContents.session.clearCache().then(focusedWindow.reload); - } - } - }, - }, { - role: 'togglefullscreen', - accelerator: process.platform === 'darwin' ? 'Ctrl+Cmd+F' : 'F11', - }, separatorItem, { - label: 'Actual Size', - accelerator: 'CmdOrCtrl+0', - click() { - mainWindow.webContents.send('zoom-reset'); - }, - }, { - label: 'Zoom In', - accelerator: 'CmdOrCtrl+SHIFT+=', - click() { - mainWindow.webContents.send('zoom-in'); - }, - }, { - label: 'Zoom Out', - accelerator: 'CmdOrCtrl+-', - click() { - mainWindow.webContents.send('zoom-out'); - }, - }, separatorItem, { - label: 'Developer Tools for Application Wrapper', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Alt+Command+I'; - } - return 'Ctrl+Shift+I'; - })(), - click(item, focusedWindow) { - if (focusedWindow) { - focusedWindow.toggleDevTools(); - } - }, - }, { - label: 'Developer Tools for Current Server', - click() { - mainWindow.webContents.send('open-devtool'); - }, - }]; - - if (process.platform !== 'darwin') { - viewSubMenu.push(separatorItem); - viewSubMenu.push({ - label: 'Toggle Dark Mode', - click() { - mainWindow.webContents.send('set-dark-mode'); - }, - }); - } - - template.push({ - label: '&View', - submenu: viewSubMenu, - }); - template.push({ - label: '&History', - submenu: [{ - label: 'Back', - accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Alt+Left', - click: (item, focusedWindow) => { - if (focusedWindow === mainWindow) { - mainWindow.webContents.send('go-back'); - } else if (focusedWindow.webContents.canGoBack()) { - focusedWindow.webContents.goBack(); - } - }, - }, { - label: 'Forward', - accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Alt+Right', - click: (item, focusedWindow) => { - if (focusedWindow === mainWindow) { - mainWindow.webContents.send('go-forward'); - } else if (focusedWindow.webContents.canGoForward()) { - focusedWindow.webContents.goForward(); - } - }, - }], - }); - - const teams = config.teams; - const windowMenu = { - label: '&Window', - submenu: [{ - role: 'minimize', - - // empty string removes shortcut on Windows; null will default by OS - accelerator: process.platform === 'win32' ? '' : null, - }, { - role: 'close', - accelerator: 'CmdOrCtrl+W', - }, separatorItem, ...teams.slice(0, 9).sort((teamA, teamB) => teamA.order - teamB.order).map((team, i) => { - return { - label: team.name, - accelerator: `CmdOrCtrl+${i + 1}`, + label: settingsLabel, + accelerator: 'CmdOrCtrl+,', click() { - mainWindow.show(); // for OS X - mainWindow.webContents.send('switch-tab', i); + WindowManager.showSettingsWindow(); }, - }; - }), separatorItem, { - label: 'Select Next Server', - accelerator: 'Ctrl+Tab', - click() { - mainWindow.webContents.send('select-next-tab'); - }, - enabled: (teams.length > 1), - }, { - label: 'Select Previous Server', - accelerator: 'Ctrl+Shift+Tab', - click() { - mainWindow.webContents.send('select-previous-tab'); - }, - enabled: (teams.length > 1), - }], - }; - template.push(windowMenu); - const submenu = []; - if (config.helpLink) { - submenu.push({ - label: 'Learn More...', - click() { - shell.openExternal(config.helpLink); - }, }); - submenu.push(separatorItem); - } - submenu.push({ - label: `Version ${app.getVersion()}`, - enabled: false, - }); - template.push({label: 'Hel&p', submenu}); - return template; + if (config.data.enableServerManagement === true) { + platformAppMenu.push({ + label: 'Sign in to Another Server', + click() { + WindowManager.sendToRenderer(ADD_SERVER); + }, + }); + } + + if (isMac) { + platformAppMenu = platformAppMenu.concat([ + separatorItem, { + role: 'hide', + }, { + role: 'hideothers', + }, { + role: 'unhide', + }, separatorItem, { + role: 'quit', + }]); + } else { + platformAppMenu = platformAppMenu.concat([ + separatorItem, { + role: 'quit', + accelerator: 'CmdOrCtrl+Q', + }]); + } + + template.push({ + label: '&' + firstMenuName, + submenu: [ + ...platformAppMenu, + ], + }); + template.push({ + label: '&Edit', + submenu: [{ + role: 'undo', + accelerator: 'CmdOrCtrl+Z', + }, { + role: 'Redo', + accelerator: 'CmdOrCtrl+SHIFT+Z', + }, separatorItem, { + role: 'cut', + accelerator: 'CmdOrCtrl+X', + }, { + role: 'copy', + accelerator: 'CmdOrCtrl+C', + }, { + role: 'paste', + accelerator: 'CmdOrCtrl+V', + }, { + role: 'pasteAndMatchStyle', + accelerator: 'CmdOrCtrl+SHIFT+V', + }, { + role: 'selectall', + accelerator: 'CmdOrCtrl+A', + }], + }); + + const viewSubMenu = [{ + label: 'Find..', + accelerator: 'CmdOrCtrl+F', + click() { + WindowManager.openFinder(); + }, + }, { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click() { + WindowManager.reload(); + }, + }, { + label: 'Clear Cache and Reload', + accelerator: 'Shift+CmdOrCtrl+R', + click() { + session.defaultSession.clearCache(); + WindowManager.reload(); + }, + }, { + role: 'togglefullscreen', + accelerator: process.platform === 'darwin' ? 'Ctrl+Cmd+F' : 'F11', + }, separatorItem, { + label: 'Actual Size', + role: 'resetZoom', + accelerator: 'CmdOrCtrl+0', + }, { + role: 'zoomIn', + accelerator: 'CmdOrCtrl+SHIFT+=', + }, { + role: 'zoomOut', + accelerator: 'CmdOrCtrl+-', + }, separatorItem, { + label: 'Developer Tools for Application Wrapper', + accelerator: (() => { + if (process.platform === 'darwin') { + return 'Alt+Command+I'; + } + return 'Ctrl+Shift+I'; + })(), + click(item, focusedWindow) { + if (focusedWindow) { + // toggledevtools opens it in the last known position, so sometimes it goes below the browserview + if (focusedWindow.isDevToolsOpened()) { + focusedWindow.closeDevTools(); + } else { + focusedWindow.openDevTools({mode: 'detach'}); + } + } + }, + }, { + label: 'Developer Tools for Current Server', + click() { + WindowManager.openBrowserViewDevTools(); + }, + }]; + + if (process.platform !== 'darwin' && process.platform !== 'win32') { + viewSubMenu.push(separatorItem); + viewSubMenu.push({ + label: 'Toggle Dark Mode', + click() { + config.toggleDarkModeManually(); + }, + }); + } + + template.push({ + label: '&View', + submenu: viewSubMenu, + }); + template.push({ + label: '&History', + submenu: [{ + label: 'Back', + accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Alt+Left', + click: () => { + const focused = webContents.getFocusedWebContents(); + if (focused.canGoBack()) { + focused.goBack(); + } + }, + }, { + label: 'Forward', + accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Alt+Right', + click: () => { + const focused = webContents.getFocusedWebContents(); + if (focused.canGoForward()) { + focused.goForward(); + } + }, + }], + }); + + const teams = config.data.teams || []; + const windowMenu = { + label: '&Window', + submenu: [{ + role: 'minimize', + + // empty string removes shortcut on Windows; null will default by OS + accelerator: process.platform === 'win32' ? '' : null, + }, { + role: 'close', + accelerator: 'CmdOrCtrl+W', + }, separatorItem, ...teams.slice(0, 9).sort((teamA, teamB) => teamA.order - teamB.order).map((team, i) => { + return { + label: team.name, + accelerator: `CmdOrCtrl+${i + 1}`, + click() { + WindowManager.switchServer(team.name, true); + }, + }; + }), separatorItem, { + label: 'Select Next Server', + accelerator: 'Ctrl+Tab', + click() { + WindowManager.sendToRenderer(SELECT_NEXT_TAB); + }, + enabled: (teams.length > 1), + }, { + label: 'Select Previous Server', + accelerator: 'Ctrl+Shift+Tab', + click() { + WindowManager.sendToRenderer(SELECT_PREVIOUS_TAB); + }, + enabled: (teams.length > 1), + }], + }; + template.push(windowMenu); + const submenu = []; + if (config.data.MenuhelpLink) { + submenu.push({ + label: 'Learn More...', + click() { + shell.openExternal(config.data.helpLink); + }, + }); + submenu.push(separatorItem); + } + submenu.push({ + // eslint-disable-next-line no-undef + label: `Version ${app.getVersion()} commit: ${__HASH_VERSION__}`, + enabled: false, + }); + + template.push({label: 'Hel&p', submenu}); + return template; } -function createMenu(mainWindow, config, isDev) { - return Menu.buildFromTemplate(createTemplate(mainWindow, config, isDev)); +function createMenu(config) { + return Menu.buildFromTemplate(createTemplate(config)); } export default { - createMenu, + createMenu, }; diff --git a/src/main/menus/tray.js b/src/main/menus/tray.js index 9fc75823..d1bd91f4 100644 --- a/src/main/menus/tray.js +++ b/src/main/menus/tray.js @@ -3,59 +3,40 @@ // See LICENSE.txt for license information. 'use strict'; -import {app, Menu} from 'electron'; +import {Menu} from 'electron'; -function createTemplate(mainWindow, config, isDev) { - const settingsURL = isDev ? 'http://localhost:8080/browser/settings.html' : `file://${app.getAppPath()}/browser/settings.html`; - const teams = config.teams; - const template = [ - ...teams.slice(0, 9).sort((teamA, teamB) => teamA.order - teamB.order).map((team, i) => { - return { - label: team.name, - click: () => { - showOrRestore(mainWindow); - mainWindow.webContents.send('switch-tab', i); +import * as WindowManager from '../windows/windowManager'; - if (process.platform === 'darwin') { - app.dock.show(); - mainWindow.focus(); - } +function createTemplate(config) { + const teams = config.teams; + const template = [ + ...teams.slice(0, 9).sort((teamA, teamB) => teamA.order - teamB.order).map((team) => { + return { + label: team.name, + click: () => { + WindowManager.switchServer(team.name, true); + }, + }; + }), { + type: 'separator', + }, { + label: process.platform === 'darwin' ? 'Preferences...' : 'Settings', + click: () => { + WindowManager.showSettingsWindow(); + }, + }, { + type: 'separator', + }, { + role: 'quit', }, - }; - }), { - type: 'separator', - }, { - label: process.platform === 'darwin' ? 'Preferences...' : 'Settings', - click: () => { - mainWindow.loadURL(settingsURL); - showOrRestore(mainWindow); - - if (process.platform === 'darwin') { - app.dock.show(); - mainWindow.focus(); - } - }, - }, { - type: 'separator', - }, { - role: 'quit', - }, - ]; - return template; + ]; + return template; } -function createMenu(mainWindow, config, isDev) { - return Menu.buildFromTemplate(createTemplate(mainWindow, config, isDev)); -} - -function showOrRestore(window) { - if (window.isMinimized()) { - window.restore(); - } else { - window.show(); - } +function createMenu(config) { + return Menu.buildFromTemplate(createTemplate(config)); } export default { - createMenu, + createMenu, }; diff --git a/src/main/notifications/Download.js b/src/main/notifications/Download.js new file mode 100644 index 00000000..161751d0 --- /dev/null +++ b/src/main/notifications/Download.js @@ -0,0 +1,32 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import path from 'path'; +import {app, Notification} from 'electron'; + +const assetsDir = path.resolve(app.getAppPath(), 'assets'); +const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); + +const defaultOptions = { + title: 'Download Complete', + silent: false, + icon: appIconURL, + urgency: 'normal', +}; + +export class DownloadNotification extends Notification { + constructor(fileName, serverInfo) { + const options = {...defaultOptions}; + if (process.platform === 'win32') { + options.icon = appIconURL; + } else if (process.platform === 'darwin') { + // Notification Center shows app's icon, so there were two icons on the notification. + Reflect.deleteProperty(options, 'icon'); + } + + options.title = process.platform === 'win32' ? serverInfo.name : 'Download Complete'; + options.body = process.platform === 'win32' ? `Download Complete \n ${fileName}` : fileName; + + super(options); + } +} diff --git a/src/main/notifications/Mention.js b/src/main/notifications/Mention.js new file mode 100644 index 00000000..8905f856 --- /dev/null +++ b/src/main/notifications/Mention.js @@ -0,0 +1,41 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import path from 'path'; +import {app, Notification} from 'electron'; + +import osVersion from 'common/osVersion'; + +const assetsDir = path.resolve(app.getAppPath(), 'assets'); +const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); + +const defaultOptions = { + title: 'Someone mentioned you', + silent: false, + icon: appIconURL, + urgency: 'normal', +}; +export const DEFAULT_WIN7 = 'Ding'; + +export class Mention extends Notification { + constructor(customOptions, channel, teamId) { + const options = {...defaultOptions, ...customOptions}; + if (process.platform === 'darwin') { + // Notification Center shows app's icon, so there were two icons on the notification. + Reflect.deleteProperty(options, 'icon'); + } + const isWin7 = (process.platform === 'win32' && osVersion.isLowerThanOrEqualWindows8_1() && DEFAULT_WIN7); + const customSound = !options.silent && ((options.data && options.data.soundName !== 'None' && options.data.soundName) || isWin7); + if (customSound) { + options.silent = true; + } + super(options); + this.customSound = customSound; + this.channel = channel; + this.teamId = teamId; + } + + getNotificationSound = () => { + return this.customSound; + } +} diff --git a/src/main/notifications/index.js b/src/main/notifications/index.js new file mode 100644 index 00000000..e04080b7 --- /dev/null +++ b/src/main/notifications/index.js @@ -0,0 +1,74 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {shell, Notification} from 'electron'; +import log from 'electron-log'; + +import {PLAY_SOUND} from 'common/communication'; + +import * as windowManager from '../windows/windowManager'; + +import {Mention} from './Mention'; +import {DownloadNotification} from './Download'; + +const currentNotifications = new Map(); + +export function displayMention(title, body, channel, teamId, silent, webcontents, data) { + if (!Notification.isSupported()) { + log.error('notification not supported'); + return; + } + const serverName = windowManager.getServerNameByWebContentsId(webcontents.id); + + const options = { + title: `${serverName}: ${title}`, + body, + silent, + data, + }; + + const mention = new Mention(options, channel, teamId); + const mentionKey = `${mention.teamId}:${mention.channel.id}`; + + mention.on('show', () => { + // On Windows, manually dismiss notifications from the same channel and only show the latest one + if (process.platform === 'win32') { + if (currentNotifications.has(mentionKey)) { + log.info(`close ${mentionKey}`); + currentNotifications.get(mentionKey).close(); + currentNotifications.delete(mentionKey); + } + currentNotifications.set(mentionKey, mention); + } + const notificationSound = mention.getNotificationSound(); + if (notificationSound) { + windowManager.sendToRenderer(PLAY_SOUND, notificationSound); + } + windowManager.flashFrame(true); + }); + + mention.on('click', () => { + if (serverName) { + windowManager.switchServer(serverName, true); + webcontents.send('notification-clicked', {channel, teamId}); + } + }); + mention.show(); +} + +export function displayDownloadCompleted(fileName, path, serverInfo) { + if (!Notification.isSupported()) { + log.error('notification not supported'); + return; + } + const download = new DownloadNotification(fileName, serverInfo); + + download.on('show', () => { + windowManager.flashFrame(true); + }); + + download.on('click', () => { + shell.showItemInFolder(path.normalize()); + }); + download.show(); +} diff --git a/src/main/preload/finderPreload.js b/src/main/preload/finderPreload.js new file mode 100644 index 00000000..5515c1c8 --- /dev/null +++ b/src/main/preload/finderPreload.js @@ -0,0 +1,35 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +'use strict'; + +import {ipcRenderer} from 'electron'; + +import {FOUND_IN_PAGE, FIND_IN_PAGE, STOP_FIND_IN_PAGE, CLOSE_FINDER, FOCUS_FINDER} from 'common/communication'; + +console.log('preloaded for the finder!'); + +window.addEventListener('message', async (event) => { + switch (event.data.type) { + case FIND_IN_PAGE: + ipcRenderer.send(FIND_IN_PAGE, event.data.data.searchText, event.data.data.options); + break; + case STOP_FIND_IN_PAGE: + ipcRenderer.send(STOP_FIND_IN_PAGE, event.data.data); + break; + case CLOSE_FINDER: + ipcRenderer.send(CLOSE_FINDER); + break; + case FOCUS_FINDER: + ipcRenderer.send(FOCUS_FINDER); + break; + default: + console.log(`got a message: ${event}`); + console.log(event); + } +}); + +ipcRenderer.on(FOUND_IN_PAGE, (event, result) => { + window.postMessage({type: FOUND_IN_PAGE, data: result}, window.location.href); +}); diff --git a/src/main/preload/loadingScreenPreload.js b/src/main/preload/loadingScreenPreload.js new file mode 100644 index 00000000..b28389bf --- /dev/null +++ b/src/main/preload/loadingScreenPreload.js @@ -0,0 +1,33 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +'use strict'; + +import {ipcRenderer} from 'electron'; + +import {RECEIVED_LOADING_SCREEN_DATA, GET_LOADING_SCREEN_DATA, LOADING_SCREEN_ANIMATION_FINISHED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication'; + +console.log('preloaded for the loading screen!'); + +window.addEventListener('message', async (event) => { + switch (event.data.type) { + case GET_LOADING_SCREEN_DATA: + window.postMessage({type: RECEIVED_LOADING_SCREEN_DATA, data: await ipcRenderer.invoke(GET_LOADING_SCREEN_DATA)}, window.location.href); + break; + case LOADING_SCREEN_ANIMATION_FINISHED: + ipcRenderer.send(LOADING_SCREEN_ANIMATION_FINISHED); + break; + default: + console.log(`got a message: ${event}`); + console.log(event); + } +}); + +ipcRenderer.on(GET_LOADING_SCREEN_DATA, (_, result) => { + window.postMessage({type: RECEIVED_LOADING_SCREEN_DATA, data: result}, window.location.href); +}); + +ipcRenderer.on(TOGGLE_LOADING_SCREEN_VISIBILITY, (_, toggle) => { + window.postMessage({type: TOGGLE_LOADING_SCREEN_VISIBILITY, data: toggle}, window.location.href); +}); diff --git a/src/main/preload/mattermost.js b/src/main/preload/mattermost.js new file mode 100644 index 00000000..b7345f69 --- /dev/null +++ b/src/main/preload/mattermost.js @@ -0,0 +1,208 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +'use strict'; + +/* eslint-disable no-magic-numbers */ + +import {ipcRenderer, webFrame} from 'electron'; +import log from 'electron-log'; + +import {NOTIFY_MENTION, IS_UNREAD, UNREAD_RESULT, SESSION_EXPIRED, SET_SERVER_NAME, REACT_APP_INITIALIZED, USER_ACTIVITY_UPDATE} from 'common/communication'; + +const UNREAD_COUNT_INTERVAL = 1000; +const CLEAR_CACHE_INTERVAL = 6 * 60 * 60 * 1000; // 6 hours + +Reflect.deleteProperty(global.Buffer); // http://electron.atom.io/docs/tutorial/security/#buffer-global + +let appVersion; +let appName; +let sessionExpired; +let serverName; + +log.info('Initializing preload'); + +ipcRenderer.invoke('get-app-version').then(({name, version}) => { + appVersion = version; + appName = name; +}); + +function isReactAppInitialized() { + const initializedRoot = + document.querySelector('#root.channel-view') || // React 16 webapp + document.querySelector('#root .signup-team__container') || // React 16 login + document.querySelector('div[data-reactroot]'); // Older React apps + if (initializedRoot === null) { + return false; + } + return initializedRoot.children.length !== 0; +} + +function watchReactAppUntilInitialized(callback) { + let count = 0; + const interval = 500; + const timeout = 30000; + const timer = setInterval(() => { + count += interval; + if (isReactAppInitialized() || count >= timeout) { // assumed as webapp has been initialized. + clearTimeout(timer); + callback(); + } + }, interval); +} + +window.addEventListener('load', () => { + if (document.getElementById('root') === null) { + console.log('The guest is not assumed as mattermost-webapp'); + return; + } + watchReactAppUntilInitialized(() => { + ipcRenderer.send(REACT_APP_INITIALIZED, serverName); + }); +}); + +const parentTag = (target) => { + if (target.parentNode) { + return target.parentNode.tagName.toUpperCase(); + } + return null; +}; + +document.addEventListener('mouseover', (event) => { + if (event.target && (event.target.tagName === 'A')) { + ipcRenderer.send('update-target-url', event.target.href); + } else if (event.target && (parentTag(event.target) === 'A')) { + ipcRenderer.send('update-target-url', event.target.parentNode.href); + } +}); + +document.addEventListener('mouseout', (event) => { + if (event.target && event.target.tagName === 'A') { + ipcRenderer.send('delete-target-url', event.target.href); + } +}); + +// listen for messages from the webapp +window.addEventListener('message', ({origin, data = {}} = {}) => { + const {type, message = {}} = data; + if (origin !== window.location.origin) { + return; + } + switch (type) { + case 'webapp-ready': { + // register with the webapp to enable custom integration functionality + console.log(`registering ${appName} v${appVersion} with the server`); + window.postMessage( + { + type: 'register-desktop', + message: { + version: appVersion, + name: appName, + }, + }, + window.location.origin || '*', + ); + break; + } + case 'register-desktop': + // it will be captured by itself too + break; + case 'dispatch-notification': { + const {title, body, channel, teamId, silent, data: messageData} = message; + ipcRenderer.send(NOTIFY_MENTION, title, body, channel, teamId, silent, messageData); + break; + } + default: + if (typeof type === 'undefined') { + console.log('ignoring message of undefined type:'); + console.log(data); + } else { + console.log(`ignored message of type: ${type}`); + } + } +}); + +const handleNotificationClick = ({channel, teamId}) => { + window.postMessage( + { + type: 'notification-clicked', + message: { + channel, + teamId, + }, + }, + window.location.origin, + ); +}; + +ipcRenderer.on('notification-clicked', (event, data) => { + handleNotificationClick(data); +}); + +const findUnread = (favicon) => { + const classes = ['team-container unreads', 'SidebarChannel unread', 'sidebar-item unread-title']; + const isUnread = classes.some((classPair) => { + const result = document.getElementsByClassName(classPair); + return result && result.length > 0; + }); + ipcRenderer.send(UNREAD_RESULT, favicon, serverName, isUnread); +}; + +ipcRenderer.on(IS_UNREAD, (event, favicon, server) => { + if (typeof serverName === 'undefined') { + serverName = server; + } + if (isReactAppInitialized()) { + findUnread(favicon); + } else { + watchReactAppUntilInitialized(() => { + findUnread(favicon); + }); + } +}); + +ipcRenderer.on(SET_SERVER_NAME, (_, name) => { + serverName = name; +}); + +function getUnreadCount() { + // LHS not found => Log out => Count should be 0, but session may be expired. + if (typeof serverName !== 'undefined') { + let isExpired; + if (document.getElementById('sidebar-left') === null) { + const extraParam = (new URLSearchParams(window.location.search)).get('extra'); + isExpired = extraParam === 'expired'; + } else { + isExpired = false; + } + if (isExpired !== sessionExpired) { + sessionExpired = isExpired; + ipcRenderer.send(SESSION_EXPIRED, sessionExpired, serverName); + } + } +} +setInterval(getUnreadCount, UNREAD_COUNT_INTERVAL); + +// push user activity updates to the webapp +ipcRenderer.on(USER_ACTIVITY_UPDATE, (event, {userIsActive, isSystemEvent}) => { + if (window.location.origin !== 'null') { + window.postMessage({type: USER_ACTIVITY_UPDATE, message: {userIsActive, manual: isSystemEvent}}, window.location.origin); + } +}); + +// exit fullscreen embedded elements like youtube - https://mattermost.atlassian.net/browse/MM-19226 +ipcRenderer.on('exit-fullscreen', () => { + if (document.fullscreenElement && document.fullscreenElement.nodeName.toLowerCase() === 'iframe') { + document.exitFullscreen(); + } +}); + +// mattermost-webapp is SPA. So cache is not cleared due to no navigation. +// We needed to manually clear cache to free memory in long-term-use. +// http://seenaburns.com/debugging-electron-memory-usage/ +setInterval(() => { + webFrame.clearCache(); +}, CLEAR_CACHE_INTERVAL); + +/* eslint-enable no-magic-numbers */ diff --git a/src/main/preload/modalPreload.js b/src/main/preload/modalPreload.js new file mode 100644 index 00000000..f1d319ee --- /dev/null +++ b/src/main/preload/modalPreload.js @@ -0,0 +1,43 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +'use strict'; + +import {ipcRenderer} from 'electron'; + +import {MODAL_CANCEL, MODAL_RESULT, MODAL_INFO, RETRIEVE_MODAL_INFO, MODAL_SEND_IPC_MESSAGE} from 'common/communication'; + +console.log('preloaded for the modal!'); + +window.addEventListener('message', async (event) => { + switch (event.data.type) { + case MODAL_CANCEL: { + console.log('canceling modal'); + ipcRenderer.send(MODAL_CANCEL, event.data.data); + break; + } + case MODAL_RESULT: { + console.log(`accepting modal with ${event.data.data}`); + ipcRenderer.send(MODAL_RESULT, event.data.data); + break; + } + case RETRIEVE_MODAL_INFO: + console.log('getting modal data'); + window.postMessage({type: MODAL_INFO, data: await ipcRenderer.invoke(RETRIEVE_MODAL_INFO)}, window.location.href); + break; + case MODAL_SEND_IPC_MESSAGE: + console.log('sending custom ipc message'); + ipcRenderer.send(event.data.data.type, ...event.data.data.args); + break; + default: + console.log(`got a message: ${event}`); + console.log(event); + } +}); + +window.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + ipcRenderer.send(MODAL_CANCEL); + } +}); diff --git a/src/main/tray/tray.js b/src/main/tray/tray.js new file mode 100644 index 00000000..ea0f7e95 --- /dev/null +++ b/src/main/tray/tray.js @@ -0,0 +1,150 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import path from 'path'; +import {app, nativeImage, nativeTheme, Tray, systemPreferences} from 'electron'; + +import {UPDATE_TRAY} from 'common/communication'; + +import * as WindowManager from '../windows/windowManager'; +import * as AppState from '../appState'; + +const assetsDir = path.resolve(app.getAppPath(), 'assets'); + +let trayImages; +let trayIcon; +let lastStatus = 'normal'; +let lastMessage = app.name; + +export function refreshTrayImages(trayIconTheme) { + switch (process.platform) { + case 'win32': + trayImages = { + normal: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray.ico')), + unread: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_unread.ico')), + mention: nativeImage.createFromPath(path.resolve(assetsDir, 'windows/tray_mention.ico')), + }; + break; + case 'darwin': + { + trayImages = { + light: { + normal: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIcon.png')), + unread: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIconUnread.png')), + mention: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/MenuIconMention.png')), + }, + clicked: { + normal: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIcon.png')), + unread: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIconUnread.png')), + mention: nativeImage.createFromPath(path.resolve(assetsDir, 'osx/ClickedMenuIconMention.png')), + }, + }; + switchMenuIconImages(trayImages, nativeTheme.shouldUseDarkColors); + break; + } + case 'linux': + { + const theme = trayIconTheme; + try { + trayImages = { + normal: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconTemplate.png')), + unread: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconUnreadTemplate.png')), + mention: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', theme, 'MenuIconMentionTemplate.png')), + }; + } catch (e) { + //Fallback for invalid theme setting + trayImages = { + normal: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconTemplate.png')), + unread: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconUnreadTemplate.png')), + mention: nativeImage.createFromPath(path.resolve(assetsDir, 'linux', 'light', 'MenuIconMentionTemplate.png')), + }; + } + break; + } + default: + trayImages = {}; + } + if (trayIcon) { + setTray(lastStatus, lastMessage); + } + return trayImages; +} + +export function switchMenuIconImages(icons, isDarkMode) { + if (isDarkMode) { + icons.normal = icons.clicked.normal; + icons.unread = icons.clicked.unread; + icons.mention = icons.clicked.mention; + } else { + icons.normal = icons.light.normal; + icons.unread = icons.light.unread; + icons.mention = icons.light.mention; + } +} + +export function setupTray(icontheme) { + refreshTrayImages(icontheme); + trayIcon = new Tray(trayImages.normal); + if (process.platform === 'darwin') { + trayIcon.setPressedImage(trayImages.clicked.normal); + systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', () => { + switchMenuIconImages(trayImages, nativeTheme.shouldUseDarkColors); + trayIcon.setImage(trayImages.normal); + }); + } + + trayIcon.setToolTip(app.name); + trayIcon.on('click', () => { + WindowManager.restoreMain(); + }); + + trayIcon.on('right-click', () => { + trayIcon.popUpContextMenu(); + }); + trayIcon.on('balloon-click', () => { + WindowManager.restoreMain(); + }); + + AppState.on(UPDATE_TRAY, (anyExpired, anyMentions, anyUnreads) => { + if (anyExpired) { + setTray('mention', 'Session Expired: Please sign in to continue receiving notifications.'); + } else if (anyMentions) { + setTray('mention', 'You have been mentioned'); + } else if (anyUnreads) { + setTray('unread', 'You have unread channels'); + } else { + setTray('normal', app.name); + } + }); +} + +function setTray(status, message) { + lastStatus = status; + lastMessage = message; + trayIcon.setImage(trayImages[status]); + if (process.platform === 'darwin') { + trayIcon.setPressedImage(trayImages.clicked[status]); + } + trayIcon.setToolTip(message); +} + +export function destroyTray() { + if (trayIcon && process.platform === 'win32') { + trayIcon.destroy(); + } +} + +export function setTrayMenu(tMenu, mainWindow) { + if (process.platform === 'darwin' || process.platform === 'linux') { + // store the information, if the tray was initialized, for checking in the settings, if the application + // was restarted after setting "Show icon on menu bar" + if (trayIcon) { + trayIcon.setContextMenu(tMenu); + mainWindow.trayWasVisible = true; + } else { + mainWindow.trayWasVisible = false; + } + } else if (trayIcon) { + trayIcon.setContextMenu(tMenu); + } +} diff --git a/src/main/trustedOrigins.js b/src/main/trustedOrigins.js index afe0ece9..4244195e 100644 --- a/src/main/trustedOrigins.js +++ b/src/main/trustedOrigins.js @@ -7,96 +7,96 @@ import fs from 'fs'; import log from 'electron-log'; -import urlUtils from '../utils/url'; +import urlUtils from '../common/utils/url'; import * as Validator from './Validator'; export default class TrustedOriginsStore { - constructor(storeFile) { - this.storeFile = storeFile; - } - - // don't use this, is for ease of mocking it on testing - readFromFile = () => { - let storeData; - try { - storeData = fs.readFileSync(this.storeFile, 'utf-8'); - } catch (e) { - storeData = null; - } - return storeData; - } - - load = () => { - const storeData = this.readFromFile(); - let result = {}; - if (storeData !== null) { - result = Validator.validateTrustedOriginsStore(storeData); - if (!result) { - throw new Error('Provided TrustedOrigins file does not validate, using defaults instead.'); - } - } - this.data = new Map(Object.entries(result)); - } - - // don't use this, is for ease of mocking it on testing - saveToFile(stringMap) { - fs.writeFileSync(this.storeFile, stringMap); - } - - save = () => { - this.saveToFile(JSON.stringify(Object.fromEntries((this.data.entries())), null, ' ')); - }; - - // if permissions or targetUrl are invalid, this function will throw an error - // this function stablishes all the permissions at once, overwriting whatever was before - // to enable just one permission use addPermission instead. - set = (targetURL, permissions) => { - const validPermissions = Validator.validateOriginPermissions(permissions); - if (!validPermissions) { - throw new Error(`Invalid permissions set for trusting ${targetURL}`); - } - this.data.set(urlUtils.getHost(targetURL), validPermissions); - }; - - // enables usage of `targetURL` for `permission` - addPermission = (targetURL, permission) => { - const origin = urlUtils.getHost(targetURL); - const currentPermissions = this.data.get(origin) || {}; - currentPermissions[permission] = true; - this.set(origin, currentPermissions); - } - - delete = (targetURL) => { - let host; - try { - host = urlUtils.getHost(targetURL); - this.data.delete(host); - } catch { - return false; - } - return true; - } - - isExisting = (targetURL) => { - return (typeof this.data.get(urlUtils.getHost(targetURL)) !== 'undefined'); - }; - - // if user hasn't set his preferences, it will return null (falsy) - checkPermission = (targetURL, permission) => { - if (!permission) { - log.error(`Missing permission request on ${targetURL}`); - return null; - } - let origin; - try { - origin = urlUtils.getHost(targetURL); - } catch (e) { - log.error(`invalid host to retrieve permissions: ${targetURL}: ${e}`); - return null; + constructor(storeFile) { + this.storeFile = storeFile; } - const urlPermissions = this.data.get(origin); - return urlPermissions ? urlPermissions[permission] : null; - } + // don't use this, is for ease of mocking it on testing + readFromFile = () => { + let storeData; + try { + storeData = fs.readFileSync(this.storeFile, 'utf-8'); + } catch (e) { + storeData = null; + } + return storeData; + } + + load = () => { + const storeData = this.readFromFile(); + let result = {}; + if (storeData !== null) { + result = Validator.validateTrustedOriginsStore(storeData); + if (!result) { + throw new Error('Provided TrustedOrigins file does not validate, using defaults instead.'); + } + } + this.data = new Map(Object.entries(result)); + } + + // don't use this, is for ease of mocking it on testing + saveToFile(stringMap) { + fs.writeFileSync(this.storeFile, stringMap); + } + + save = () => { + this.saveToFile(JSON.stringify(Object.fromEntries((this.data.entries())), null, ' ')); + }; + + // if permissions or targetUrl are invalid, this function will throw an error + // this function stablishes all the permissions at once, overwriting whatever was before + // to enable just one permission use addPermission instead. + set = (targetURL, permissions) => { + const validPermissions = Validator.validateOriginPermissions(permissions); + if (!validPermissions) { + throw new Error(`Invalid permissions set for trusting ${targetURL}`); + } + this.data.set(urlUtils.getHost(targetURL), validPermissions); + }; + + // enables usage of `targetURL` for `permission` + addPermission = (targetURL, permission) => { + const origin = urlUtils.getHost(targetURL); + const currentPermissions = this.data.get(origin) || {}; + currentPermissions[permission] = true; + this.set(origin, currentPermissions); + } + + delete = (targetURL) => { + let host; + try { + host = urlUtils.getHost(targetURL); + this.data.delete(host); + } catch { + return false; + } + return true; + } + + isExisting = (targetURL) => { + return (typeof this.data.get(urlUtils.getHost(targetURL)) !== 'undefined'); + }; + + // if user hasn't set his preferences, it will return null (falsy) + checkPermission = (targetURL, permission) => { + if (!permission) { + log.error(`Missing permission request on ${targetURL}`); + return null; + } + let origin; + try { + origin = urlUtils.getHost(targetURL); + } catch (e) { + log.error(`invalid host to retrieve permissions: ${targetURL}: ${e}`); + return null; + } + + const urlPermissions = this.data.get(origin); + return urlPermissions ? urlPermissions[permission] : null; + } } diff --git a/src/main/utils.js b/src/main/utils.js index 5baaa415..aef29baa 100644 --- a/src/main/utils.js +++ b/src/main/utils.js @@ -2,16 +2,72 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {app} from 'electron'; +import electron, {app} from 'electron'; +import path from 'path'; + +import {PRODUCTION} from 'common/utils/constants'; +import Utils from 'common/utils/util'; + +const TAB_BAR_HEIGHT = 40; +const BACK_BAR_HEIGHT = 36; export function shouldBeHiddenOnStartup(parsedArgv) { - if (parsedArgv.hidden) { - return true; - } - if (process.platform === 'darwin') { - if (app.getLoginItemSettings().wasOpenedAsHidden) { - return true; + if (parsedArgv.hidden) { + return true; } - } - return false; + if (process.platform === 'darwin') { + if (app.getLoginItemSettings().wasOpenedAsHidden) { + return true; + } + } + return false; +} + +export function getWindowBoundaries(win, hasBackBar = false) { + const {width, height} = win.getContentBounds(); + return getAdjustedWindowBoundaries(width, height, hasBackBar); +} + +export function getAdjustedWindowBoundaries(width, height, hasBackBar = false) { + return { + x: 0, + y: TAB_BAR_HEIGHT + (hasBackBar ? BACK_BAR_HEIGHT : 0), + width, + height: height - TAB_BAR_HEIGHT - (hasBackBar ? BACK_BAR_HEIGHT : 0), + }; +} + +export function getLocalURLString(urlPath, query, isMain) { + const localURL = getLocalURL(urlPath, query, isMain); + return localURL.href; +} + +export function getLocalURL(urlPath, query, isMain) { + let pathname; + const processPath = isMain ? '' : '/renderer'; + const mode = Utils.runMode(); + const protocol = 'file'; + const hostname = ''; + const port = ''; + if (mode === PRODUCTION) { + pathname = path.join(electron.app.getAppPath(), `${processPath}/${urlPath}`); + } else { + pathname = path.resolve(__dirname, `../../dist/${processPath}/${urlPath}`); // TODO: find a better way to work with webpack on this + } + const localUrl = new URL(`${protocol}://${hostname}${port}`); + localUrl.pathname = pathname; + if (query) { + query.forEach((value, key) => { + localUrl.searchParams.append(encodeURIComponent(key), encodeURIComponent(value)); + }); + } + + return localUrl; +} + +export function getLocalPreload(file) { + if (Utils.runMode() === PRODUCTION) { + return path.join(electron.app.getAppPath(), `${file}`); + } + return path.resolve(__dirname, `../../dist/${file}`); } diff --git a/src/main/views/MattermostView.js b/src/main/views/MattermostView.js new file mode 100644 index 00000000..937ef343 --- /dev/null +++ b/src/main/views/MattermostView.js @@ -0,0 +1,303 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {BrowserView, app, ipcMain} from 'electron'; +import log from 'electron-log'; + +import {EventEmitter} from 'events'; + +import {RELOAD_INTERVAL, MAX_SERVER_RETRIES, SECOND} from 'common/utils/constants'; +import urlUtils from 'common/utils/url'; +import {LOAD_RETRY, LOAD_SUCCESS, LOAD_FAILED, UPDATE_TARGET_URL, IS_UNREAD, UNREAD_RESULT, TOGGLE_BACK_BUTTON, SET_SERVER_NAME} from 'common/communication'; + +import {getWindowBoundaries, getLocalPreload} from '../utils'; +import * as WindowManager from '../windows/windowManager'; +import * as appState from '../appState'; + +// copying what webview sends +// TODO: review +const userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.146 Electron/6.1.7 Safari/537.36 Mattermost/${app.getVersion()}`; +const READY = 1; +const LOADING = 0; +const ERROR = -1; + +const ASTERISK_GROUP = 3; +const MENTIONS_GROUP = 2; + +export class MattermostView extends EventEmitter { + constructor(server, win, options) { + super(); + this.server = server; + this.window = win; + + const preload = getLocalPreload('preload.js'); + const spellcheck = ((!options || typeof options.spellcheck === 'undefined') ? true : options.spellcheck); + this.options = { + webPreferences: { + contextIsolation: process.env.NODE_ENV !== 'test', + preload, + spellcheck, + additionalArguments: [ + `version=${app.version}`, + `appName=${app.name}`, + ], + enableRemoteModule: true, + nodeIntegration: process.env.NODE_ENV === 'test', + }, + ...options, + }; + this.isVisible = false; + this.view = new BrowserView(this.options); + this.resetLoadingStatus(); + + /** + * for backward compatibility when reading the title. + * null means we have yet to figure out if it uses it or not but we consider it false until proven wrong + */ + this.usesAsteriskForUnreads = null; + + this.faviconMemoize = new Map(); + this.currentFavicon = null; + log.info(`BrowserView created for server ${this.server.name}`); + + this.isInitialized = false; + this.hasBeenShown = false; + + if (process.platform !== 'darwin') { + this.altLastPressed = false; + this.view.webContents.on('before-input-event', this.handleInputEvents); + } + } + + // use the same name as the server + // TODO: we'll need unique identifiers if we have multiple instances of the same server in different tabs (1:N relationships) + get name() { + return this.server.name; + } + + resetLoadingStatus = () => { + if (this.status !== LOADING) { // if it's already loading, don't touch anything + this.retryLoad = null; + this.status = LOADING; + this.maxRetries = MAX_SERVER_RETRIES; + } + } + + load = (someURL) => { + const loadURL = (typeof someURL === 'undefined') ? `${this.server.url.toString()}` : urlUtils.parseURL(someURL).toString(); + log.info(`[${this.server.name}] Loading ${loadURL}`); + const loading = this.view.webContents.loadURL(loadURL, {userAgent}); + loading.then(this.loadSuccess(loadURL)).catch((err) => { + this.loadRetry(loadURL, err); + }); + } + + retry = (loadURL) => { + return () => { + // window was closed while retrying + if (!this.view) { + return; + } + const loading = this.view.webContents.loadURL(loadURL, {userAgent}); + loading.then(this.loadSuccess(loadURL)).catch((err) => { + if (this.maxRetries-- > 0) { + this.loadRetry(loadURL, err); + } else { + WindowManager.sendToRenderer(LOAD_FAILED, this.server.name, err.toString(), loadURL.toString()); + this.emit(LOAD_FAILED, this.server.name, err.toString(), loadURL.toString()); + log.info(`[${this.server.name}] Couldn't stablish a connection with ${loadURL}: ${err}.`); + this.status = ERROR; + } + }); + }; + } + + loadRetry = (loadURL, err) => { + this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL); + WindowManager.sendToRenderer(LOAD_RETRY, this.server.name, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString()); + log.info(`[${this.server.name}] failed loading ${loadURL}: ${err}, retrying in ${RELOAD_INTERVAL / SECOND} seconds`); + } + + loadSuccess = (loadURL) => { + return () => { + log.info(`[${this.server.name}] finished loading ${loadURL}`); + WindowManager.sendToRenderer(LOAD_SUCCESS, this.server.name); + this.maxRetries = MAX_SERVER_RETRIES; + if (this.status === LOADING) { + ipcMain.on(UNREAD_RESULT, this.handleFaviconIsUnread); + this.handleTitleUpdate(null, this.view.webContents.getTitle()); + this.findUnreadState(null); + } + this.status = READY; + this.emit(LOAD_SUCCESS, this.server.name, loadURL.toString()); + this.view.webContents.send(SET_SERVER_NAME, this.server.name); + }; + } + + show = (requestedVisibility) => { + this.hasBeenShown = true; + const request = typeof requestedVisibility === 'undefined' ? true : requestedVisibility; + if (request && !this.isVisible) { + this.window.addBrowserView(this.view); + this.setBounds(getWindowBoundaries(this.window, !(urlUtils.isTeamUrl(this.server.url, this.view.webContents.getURL()) || urlUtils.isAdminUrl(this.server.url, this.view.webContents.getURL())))); + if (this.status === READY) { + this.focus(); + } + } else if (!request && this.isVisible) { + this.window.removeBrowserView(this.view); + } + this.isVisible = request; + } + + reload = () => { + this.resetLoadingStatus(); + this.load(); + } + + hide = () => this.show(false); + + setBounds = (boundaries) => { + // todo: review this, as it might not work properly with devtools/minimizing/resizing + this.view.setBounds(boundaries); + } + + destroy = () => { + if (this.retryLoad) { + clearTimeout(this.retryLoad); + } + if (this.window) { + this.window.removeBrowserView(this.view); + } + this.window = null; + this.server = null; + this.isVisible = false; + clearTimeout(this.retryLoad); + } + + focus = () => { + if (this.view.webContents) { + this.view.webContents.focus(); + } else { + log.warn('trying to focus the browserview, but it doesn\'t yet have webcontents.'); + } + } + + isReady = () => { + return this.status === READY; + } + + needsLoadingScreen = () => { + return !(this.isInitialized && this.hasBeenShown); + } + + setInitialized = () => { + this.isInitialized = true; + } + + openDevTools = () => { + this.view.webContents.openDevTools({mode: 'detach'}); + } + + getWebContents = () => { + if (this.status === READY) { + return this.view.webContents; + } else if (this.window) { + return this.window.webContents; // if it's not ready you are looking at the renderer process + } + return WindowManager.getMainWindow.webContents; + } + + handleInputEvents = (_, input) => { + // Handler for pressing the Alt key to focus the 3-dot menu + if (input.key === 'Alt' && input.type === 'keyUp' && this.altLastPressed) { + this.altLastPressed = false; + WindowManager.focusThreeDotMenu(); + return; + } + + // Hack to detect keyPress so that alt+ combinations don't default back to the 3-dot menu + if (input.key === 'Alt' && input.type === 'keyDown') { + this.altLastPressed = true; + } else { + this.altLastPressed = false; + } + } + + handleDidNavigate = (event, url) => { + const isUrlTeamUrl = urlUtils.isTeamUrl(this.server.url, url) || urlUtils.isAdminUrl(this.server.url, url); + if (isUrlTeamUrl) { + this.setBounds(getWindowBoundaries(this.window)); + WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, false); + log.info('hide back button'); + } else { + this.setBounds(getWindowBoundaries(this.window, true)); + WindowManager.sendToRenderer(TOGGLE_BACK_BUTTON, true); + log.info('show back button'); + } + } + + handleUpdateTarget = (e, url) => { + if (!this.server.sameOrigin(url)) { + this.emit(UPDATE_TARGET_URL, url); + } + } + + handleFoundInPage = (event, result) => WindowManager.foundInPage(result) + + titleParser = /(\((\d+)\) )?(\*)?/g + + handleTitleUpdate = (e, title) => { + //const title = this.view.webContents.getTitle(); + const resultsIterator = title.matchAll(this.titleParser); + const results = resultsIterator.next(); // we are only interested in the first set + + // if not using asterisk (version > v5.28), it'll be marked as undefined and wont be used to check if there are unread channels + const hasAsterisk = results && results.value && results.value[ASTERISK_GROUP]; + if (typeof hasAsterisk !== 'undefined') { + this.usesAsteriskForUnreads = true; + } + let unreads; + if (this.usesAsteriskForUnreads) { + unreads = Boolean(hasAsterisk); + } + const mentions = (results && results.value && parseInt(results.value[MENTIONS_GROUP], 10)) || 0; + + appState.updateMentions(this.server.name, mentions, unreads); + } + + handleFaviconUpdate = (e, favicons) => { + if (!this.usesAsteriskForUnreads) { + // if unread state is stored for that favicon, retrieve value. + // if not, get related info from preload and store it for future changes + this.currentFavicon = favicons[0]; + if (this.faviconMemoize.has(favicons[0])) { + appState.updateUnreads(this.server.name, this.faviconMemoize.get(favicons[0])); + } else { + this.findUnreadState(favicons[0]); + } + } + } + + // if favicon is null, it will affect appState, but won't be memoized + findUnreadState = (favicon) => { + try { + this.view.webContents.send(IS_UNREAD, favicon, this.server.name); + } catch (err) { + log.error(`There was an error trying to request the unread state: ${err}`); + log.error(err.stack); + } + } + + // if favicon is null, it means it is the initial load, + // so don't memoize as we don't have the favicons and there is no rush to find out. + handleFaviconIsUnread = (e, favicon, serverName, result) => { + if (this.server && serverName === this.server.name) { + if (favicon) { + this.faviconMemoize.set(favicon, result); + } + if (favicon === null || favicon === this.currentFavicon) { + appState.updateUnreads(serverName, result); + } + } + } +} diff --git a/src/main/views/modalManager.js b/src/main/views/modalManager.js new file mode 100644 index 00000000..ac24c05e --- /dev/null +++ b/src/main/views/modalManager.js @@ -0,0 +1,109 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {ipcMain} from 'electron'; + +import {RETRIEVE_MODAL_INFO, MODAL_CANCEL, MODAL_RESULT, MODAL_OPEN, MODAL_CLOSE} from 'common/communication.js'; + +import * as WindowManager from '../windows/windowManager'; + +import {ModalView} from './modalView'; + +let modalQueue = []; + +// TODO: add a queue/add differentiation, in case we need to put a modal first in line +// should we return the original promise if called multiple times with the same key? +export function addModal(key, html, preload, data, win) { + const foundModal = modalQueue.find((modal) => modal.key === key); + if (!foundModal) { + const modalPromise = new Promise((resolve, reject) => { + const mv = new ModalView(key, html, preload, data, resolve, reject, win); + modalQueue.push(mv); + }); + + if (modalQueue.length === 1) { + showModal(); + } + + return modalPromise; + } + return null; +} + +ipcMain.handle(RETRIEVE_MODAL_INFO, handleInfoRequest); +ipcMain.on(MODAL_RESULT, handleModalResult); +ipcMain.on(MODAL_CANCEL, handleModalCancel); + +function findModalByCaller(event) { + if (modalQueue.length) { + const requestModal = modalQueue.find((modal) => { + return (modal.view && modal.view.webContents && modal.view.webContents.id === event.sender.id); + }); + return requestModal; + } + return null; +} + +function handleInfoRequest(event) { + const requestModal = findModalByCaller(event); + if (requestModal) { + return requestModal.handleInfoRequest(); + } + return null; +} + +export function showModal() { + let noWindow; + const withDevTools = process.env.MM_DEBUG_MODALS || false; + modalQueue.forEach((modal, index) => { + if (index === 0) { + WindowManager.sendToRenderer(MODAL_OPEN); + modal.show(noWindow, withDevTools); + } else { + WindowManager.sendToRenderer(MODAL_CLOSE); + modal.hide(); + } + }); +} + +function handleModalResult(event, data) { + const requestModal = findModalByCaller(event); + if (requestModal) { + requestModal.resolve(data); + } + filterActive(); + if (modalQueue.length) { + showModal(); + } else { + WindowManager.sendToRenderer(MODAL_CLOSE); + WindowManager.focusBrowserView(); + } +} + +function handleModalCancel(event, data) { + const requestModal = findModalByCaller(event); + if (requestModal) { + requestModal.reject(data); + } + filterActive(); + if (modalQueue.length) { + showModal(); + } else { + WindowManager.sendToRenderer(MODAL_CLOSE); + WindowManager.focusBrowserView(); + } +} + +function filterActive() { + modalQueue = modalQueue.filter((modal) => modal.isActive()); +} + +export function isModalDisplayed() { + return modalQueue.some((modal) => modal.isActive()); +} + +export function focusCurrentModal() { + if (isModalDisplayed()) { + modalQueue[0].view.webContents.focus(); + } +} diff --git a/src/main/views/modalView.js b/src/main/views/modalView.js new file mode 100644 index 00000000..19392cb4 --- /dev/null +++ b/src/main/views/modalView.js @@ -0,0 +1,101 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {BrowserView} from 'electron'; +import log from 'electron-log'; + +import {getWindowBoundaries} from '../utils'; + +const ACTIVE = 'active'; +const SHOWING = 'showing'; +const DONE = 'done'; + +export class ModalView { + constructor(key, html, preload, data, onResolve, onReject, currentWindow) { + this.key = key; + this.html = html; + this.data = data; + log.info(`preloading with ${preload}`); + this.view = new BrowserView({webPreferences: { + contextIsolation: process.env.NODE_ENV !== 'test', + preload, + nodeIntegration: process.env.NODE_ENV === 'test', + enableRemoteModule: process.env.NODE_ENV === 'test', + }}); + this.onReject = onReject; + this.onResolve = onResolve; + this.window = currentWindow; + this.windowAttached = null; + this.status = ACTIVE; + try { + this.view.webContents.loadURL(this.html); + } catch (e) { + log.error('there was an error loading the modal:'); + log.error(e); + } + } + + show = (win, withDevTools) => { + if (this.windowAttached) { + // we'll reatach + this.windowAttached.removeBrowserView(this.view); + } + this.windowAttached = win || this.window; + + this.windowAttached.addBrowserView(this.view); + this.view.setBounds(getWindowBoundaries(this.windowAttached)); + this.view.setAutoResize({ + height: true, + width: true, + horizontal: true, + vertical: true, + }); + this.status = SHOWING; + if (this.view.webContents.isLoading()) { + this.view.webContents.once('did-finish-load', () => { + this.view.webContents.focus(); + }); + } else { + this.view.webContents.focus(); + } + + if (withDevTools) { + log.info(`showing dev tools for ${this.key}`); + this.view.webContents.openDevTools({mode: 'detach'}); + } + } + + hide = () => { + if (this.windowAttached) { + if (this.view.webContents.isDevToolsOpened()) { + this.view.webContents.closeDevTools(); + } + + this.windowAttached.removeBrowserView(this.view); + this.windowAttached = null; + this.status = ACTIVE; + } + } + + handleInfoRequest = () => { + return this.data; + } + + reject = (data) => { + if (this.onReject) { + this.onReject(data); + } + this.hide(); + this.status = DONE; + } + + resolve = (data) => { + if (this.onResolve) { + this.onResolve(data); + } + this.hide(); + this.status = DONE; + } + + isActive = () => this.status !== DONE; +} diff --git a/src/main/views/viewManager.js b/src/main/views/viewManager.js new file mode 100644 index 00000000..42d614d9 --- /dev/null +++ b/src/main/views/viewManager.js @@ -0,0 +1,367 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import log from 'electron-log'; +import {BrowserView, dialog} from 'electron'; + +import {SECOND} from 'common/utils/constants'; +import {UPDATE_TARGET_URL, FOUND_IN_PAGE, SET_SERVER_KEY, LOAD_SUCCESS, LOAD_FAILED, TOGGLE_LOADING_SCREEN_VISIBILITY, GET_LOADING_SCREEN_DATA} from 'common/communication'; +import urlUtils from 'common/utils/url'; + +import contextMenu from '../contextMenu'; +import {MattermostServer} from '../MattermostServer'; +import {getLocalURLString, getLocalPreload, getWindowBoundaries} from '../utils'; + +import {MattermostView} from './MattermostView'; +import {showModal, isModalDisplayed, focusCurrentModal} from './modalManager'; +import {addWebContentsEventListeners} from './webContentEvents'; + +const URL_VIEW_DURATION = 10 * SECOND; +const URL_VIEW_HEIGHT = 36; +const FINDER_WIDTH = 310; +const FINDER_HEIGHT = 40; + +export class ViewManager { + constructor(config, mainWindow) { + this.configServers = config.teams; + this.viewOptions = {spellcheck: config.useSpellChecker}; + this.views = new Map(); // keep in mind that this doesn't need to hold server order, only tabs on the renderer need that. + this.currentView = null; + this.urlView = null; + this.mainWindow = mainWindow; + } + + updateMainWindow = (mainWindow) => { + this.mainWindow = mainWindow; + } + + getServers = () => { + return this.configServers; + } + + loadServer = (server) => { + const srv = new MattermostServer(server.name, server.url); + const view = new MattermostView(srv, this.mainWindow, this.viewOptions); + this.views.set(server.name, view); + if (!this.loadingScreen) { + this.createLoadingScreen(); + } + view.once(LOAD_SUCCESS, this.activateView); + view.load(); + view.on(UPDATE_TARGET_URL, this.showURLView); + } + + load = () => { + this.configServers.forEach((server) => this.loadServer(server)); + } + + reloadConfiguration = (configServers) => { + this.configServers = configServers.concat(); + const oldviews = this.views; + this.views = new Map(); + const sorted = this.configServers.sort((a, b) => a.order - b.order); + let setFocus; + sorted.forEach((server) => { + const recycle = oldviews.get(server.name); + if (recycle && recycle.isVisible) { + setFocus = recycle.name; + } + if (recycle && recycle.server.url.toString() === urlUtils.parseURL(server.url).toString()) { + oldviews.delete(recycle.name); + this.views.set(recycle.name, recycle); + } else { + this.loadServer(server, this.mainWindow); + } + }); + oldviews.forEach((unused) => { + unused.destroy(); + }); + if (setFocus) { + this.showByName(setFocus); + } else { + this.showInitial(); + } + } + + showInitial = () => { + if (this.configServers.length) { + const element = this.configServers.find((e) => e.order === 0); + if (element) { + this.showByName(element.name); + } + } + } + + showByName = (name) => { + const newView = this.views.get(name); + if (newView.isVisible) { + return; + } + if (newView) { + if (this.currentView && this.currentView !== name) { + const previous = this.getCurrentView(); + if (previous) { + previous.hide(); + } + } + + this.currentView = name; + if (newView.needsLoadingScreen()) { + this.showLoadingScreen(); + } + const serverInfo = this.configServers.find((candidate) => candidate.name === newView.server.name); + newView.window.webContents.send(SET_SERVER_KEY, serverInfo.order); + if (newView.isReady()) { + // if view is not ready, the renderer will have something to display instead. + newView.show(); + if (newView.needsLoadingScreen()) { + this.showLoadingScreen(); + } else { + this.fadeLoadingScreen(); + } + contextMenu.reload(newView.getWebContents()); + } else { + log.warn(`couldn't show ${name}, not ready`); + } + } else { + log.warn(`Couldn't find a view with name: ${name}`); + } + showModal(); + } + + focus = () => { + if (isModalDisplayed()) { + focusCurrentModal(); + return; + } + + const view = this.getCurrentView(); + if (view) { + view.focus(); + } + } + activateView = (viewName) => { + if (this.currentView === viewName) { + this.showByName(this.currentView); + } + const view = this.views.get(viewName); + addWebContentsEventListeners(view, this.getServers); + } + + getCurrentView() { + return this.views.get(this.currentView); + } + + openViewDevTools = () => { + const view = this.getCurrentView(); + if (view) { + view.openDevTools({mode: 'detach'}); + } else { + log.error(`couldn't find ${this.currentView}`); + } + } + + findByWebContent(webContentId) { + let found = null; + let serverName; + let view; + const entries = this.views.entries(); + + for ([serverName, view] of entries) { + if (typeof serverName !== 'undefined') { + const wc = view.getWebContents(); + if (wc && wc.id === webContentId) { + found = serverName; + } + } + } + return found; + } + + showURLView = (url) => { + if (this.urlViewCancel) { + this.urlViewCancel(); + } + if (url && url !== '') { + const urlString = typeof url === 'string' ? url : url.toString(); + const urlView = new BrowserView({ + webPreferences: { + contextIsolation: process.env.NODE_ENV !== 'test', + nodeIntegration: process.env.NODE_ENV === 'test', + enableRemoteModule: process.env.NODE_ENV === 'test', + }}); + const query = new Map([['url', urlString]]); + const localURL = getLocalURLString('urlView.html', query); + urlView.webContents.loadURL(localURL); + const currentWindow = this.getCurrentView().window; + currentWindow.addBrowserView(urlView); + const boundaries = currentWindow.getBounds(); + urlView.setBounds({ + x: 0, + y: boundaries.height - URL_VIEW_HEIGHT, + width: Math.floor(boundaries.width / 3), + height: URL_VIEW_HEIGHT, + }); + + const hideView = () => { + this.urlViewCancel = null; + currentWindow.removeBrowserView(urlView); + }; + + const timeout = setTimeout(hideView, + URL_VIEW_DURATION); + + this.urlViewCancel = () => { + clearTimeout(timeout); + hideView(); + }; + } + } + + setFinderBounds = () => { + if (this.finder) { + const boundaries = this.mainWindow.getBounds(); + this.finder.setBounds({ + x: boundaries.width - FINDER_WIDTH - (process.platform === 'darwin' ? 20 : 200), + y: 0, + width: FINDER_WIDTH, + height: FINDER_HEIGHT, + }); + } + } + + focusFinder = () => { + if (this.finder) { + this.finder.webContents.focus(); + } + } + + hideFinder = () => { + if (this.finder) { + this.mainWindow.removeBrowserView(this.finder); + this.finder = null; + } + } + + foundInPage = (result) => { + if (this.finder) { + this.finder.webContents.send(FOUND_IN_PAGE, result); + } + }; + + showFinder = () => { + // just focus the current finder if it's already open + if (this.finder) { + this.finder.webContents.focus(); + return; + } + + const preload = getLocalPreload('finderPreload.js'); + this.finder = new BrowserView({webPreferences: { + contextIsolation: process.env.NODE_ENV !== 'test', + preload, + nodeIntegration: process.env.NODE_ENV === 'test', + enableRemoteModule: process.env.NODE_ENV === 'test', // TODO: try to use this only on testing + }}); + const localURL = getLocalURLString('finder.html'); + this.finder.webContents.loadURL(localURL); + this.mainWindow.addBrowserView(this.finder); + this.setFinderBounds(); + + this.finder.webContents.focus(); + }; + + setLoadingScreenBounds = () => { + if (this.loadingScreen) { + this.loadingScreen.setBounds(getWindowBoundaries(this.mainWindow)); + } + } + + createLoadingScreen = () => { + const preload = getLocalPreload('loadingScreenPreload.js'); + this.loadingScreen = new BrowserView({webPreferences: { + contextIsolation: true, + preload, + }}); + const localURL = getLocalURLString('loadingScreen.html'); + this.loadingScreen.webContents.loadURL(localURL); + } + + showLoadingScreen = () => { + if (!this.loadingScreen) { + this.createLoadingScreen(); + } + + this.loadingScreen.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, true); + + if (this.mainWindow.getBrowserViews().includes(this.loadingScreen)) { + this.mainWindow.setTopBrowserView(this.loadingScreen); + } else { + this.mainWindow.addBrowserView(this.loadingScreen); + } + + this.setLoadingScreenBounds(); + } + + fadeLoadingScreen = () => { + if (this.loadingScreen) { + this.loadingScreen.webContents.send(TOGGLE_LOADING_SCREEN_VISIBILITY, false); + } + } + + hideLoadingScreen = () => { + if (this.loadingScreen) { + this.mainWindow.removeBrowserView(this.loadingScreen); + } + } + + setServerInitialized = (server) => { + const view = this.views.get(server); + if (view) { + view.setInitialized(); + if (this.getCurrentView() === view) { + this.fadeLoadingScreen(); + } + } + } + + updateLoadingScreenDarkMode = (darkMode) => { + if (this.loadingScreen) { + this.loadingScreen.webContents.send(GET_LOADING_SCREEN_DATA, {darkMode}); + } + } + + deeplinkSuccess = (serverName) => { + const view = this.views.get(serverName); + this.showByName(serverName); + view.removeListener(LOAD_FAILED, this.deeplinkFailed); + }; + + deeplinkFailed = (serverName, err, url) => { + const view = this.views.get(serverName); + log.error(`[${serverName}] failed to load deeplink ${url}: ${err}`); + view.removeListener(LOAD_SUCCESS, this.deeplinkSuccess); + } + + handleDeepLink = (url) => { + if (url) { + const parsedURL = urlUtils.parseURL(url); + const server = urlUtils.getServer(parsedURL, this.configServers, true); + if (server) { + const view = this.views.get(server.name); + + // attempting to change parsedURL protocol results in it not being modified. + const urlWithSchema = `${view.server.url.origin}${parsedURL.pathname}${parsedURL.search}`; + view.resetLoadingStatus(); + view.load(urlWithSchema); + view.once(LOAD_SUCCESS, this.deeplinkSuccess); + view.once(LOAD_FAILED, this.deeplinkFailed); + } else { + dialog.showErrorBox('No matching server', `there is no configured server in the app that matches the requested url: ${parsedURL.toString()}`); + } + } + }; + + sendToAllViews = (channel, ...args) => { + this.views.forEach((view) => view.view.webContents.send(channel, ...args)); + } +} diff --git a/src/main/views/webContentEvents.js b/src/main/views/webContentEvents.js new file mode 100644 index 00000000..0adf9ef6 --- /dev/null +++ b/src/main/views/webContentEvents.js @@ -0,0 +1,250 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {BrowserWindow, shell} from 'electron'; +import log from 'electron-log'; + +import {DEVELOPMENT, PRODUCTION} from 'common/utils/constants'; +import urlUtils from 'common/utils/url'; +import Utils from 'common/utils/util'; +import {FOUND_IN_PAGE} from 'common/communication'; + +import * as WindowManager from '../windows/windowManager'; + +import {protocols} from '../../../electron-builder.json'; + +import allowProtocolDialog from '../allowProtocolDialog'; + +const customLogins = {}; +const listeners = {}; +let popupWindow = null; + +function isTrustedPopupWindow(webContents) { + if (!webContents) { + return false; + } + if (!popupWindow) { + return false; + } + return Utils.browserWindowFromWebContents(webContents) === popupWindow; +} + +const nixUA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari/537.36'; + +const popupUserAgent = { + darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari/537.36', + win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome Safari/537.36', + aix: nixUA, + freebsd: nixUA, + linux: nixUA, + openbsd: nixUA, + sunos: nixUA, +}; + +const scheme = protocols && protocols[0] && protocols[0].schemes && protocols[0].schemes[0]; + +const generateWillNavigate = (getServersFunction) => { + return (event, url) => { + const contentID = event.sender.id; + const parsedURL = urlUtils.parseURL(url); + const configServers = getServersFunction(); + const server = urlUtils.getServer(parsedURL, configServers); + + if (server && (urlUtils.isTeamUrl(server.url, parsedURL) || urlUtils.isAdminUrl(server.url, parsedURL) || isTrustedPopupWindow(event.sender))) { + return; + } + + if (urlUtils.isCustomLoginURL(parsedURL, server, configServers)) { + return; + } + if (parsedURL.protocol === 'mailto:') { + return; + } + if (customLogins[contentID].inProgress) { + return; + } + const mode = Utils.runMode(); + if (((mode === DEVELOPMENT || mode === PRODUCTION) && + (parsedURL.path === 'renderer/index.html' || parsedURL.path === 'renderer/settings.html'))) { + log.info('loading settings page'); + return; + } + + log.info(`Prevented desktop from navigating to: ${url}`); + event.preventDefault(); + }; +}; + +const generateDidStartNavigation = (getServersFunction) => { + return (event, url) => { + const serverList = getServersFunction(); + const contentID = event.sender.id; + const parsedURL = urlUtils.parseURL(url); + const server = urlUtils.getServer(parsedURL, serverList); + + if (!urlUtils.isTrustedURL(parsedURL, serverList)) { + return; + } + + if (urlUtils.isCustomLoginURL(parsedURL, server, serverList)) { + customLogins[contentID].inProgress = true; + } else if (customLogins[contentID].inProgress) { + customLogins[contentID].inProgress = false; + } + }; +}; + +const generateNewWindowListener = (getServersFunction, spellcheck) => { + return (event, url) => { + const parsedURL = urlUtils.parseURL(url); + const configServers = getServersFunction(); + + // Dev tools case + if (parsedURL.protocol === 'devtools:') { + return; + } + event.preventDefault(); + + // Check for valid URL + if (!urlUtils.isValidURI(url)) { + return; + } + + // Check for custom protocol + if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:' && parsedURL.protocol !== `${scheme}:`) { + allowProtocolDialog.handleDialogEvent(parsedURL.protocol, url); + return; + } + + const server = urlUtils.getServer(parsedURL, configServers); + + if (!server) { + shell.openExternal(url); + return; + } + + // Public download links case + // TODO: We might be handling different types differently in the future, for now + // we are going to mimic the browser and just pop a new browser window for public links + if (parsedURL.pathname.match(/^(\/api\/v[3-4]\/public)*\/files\//)) { + shell.openExternal(url); + return; + } + + if (parsedURL.pathname.match(/^\/help\//)) { + // Help links case + // continue to open special case internal urls in default browser + shell.openExternal(url); + return; + } + + if (urlUtils.isTeamUrl(server.url, parsedURL, true)) { + WindowManager.showMainWindow(parsedURL); + return; + } + if (urlUtils.isAdminUrl(server.url, parsedURL)) { + log.info(`${url} is an admin console page, preventing to open a new window`); + return; + } + if (popupWindow && !popupWindow.closed && popupWindow.getURL() === url) { + log.info(`Popup window already open at provided url: ${url}`); + return; + } + + // TODO: move popups to its own and have more than one. + if (urlUtils.isPluginUrl(server.url, parsedURL) || urlUtils.isManagedResource(server.url, parsedURL)) { + if (!popupWindow || popupWindow.closed) { + popupWindow = new BrowserWindow({ + backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do + //parent: WindowManager.getMainWindow(), + show: false, + center: true, + webPreferences: { + nodeIntegration: process.env.NODE_ENV === 'test', + contextIsolation: process.env.NODE_ENV !== 'test', + spellcheck: (typeof spellcheck === 'undefined' ? true : spellcheck), + enableRemoteModule: process.env.NODE_ENV === 'test', + }, + }); + popupWindow.once('ready-to-show', () => { + popupWindow.show(); + }); + popupWindow.once('closed', () => { + popupWindow = null; + }); + } + + if (urlUtils.isManagedResource(server.url, parsedURL)) { + popupWindow.loadURL(url); + } else { + // currently changing the userAgent for popup windows to allow plugins to go through google's oAuth + // should be removed once a proper oAuth2 implementation is setup. + popupWindow.loadURL(url, { + userAgent: popupUserAgent[process.platform], + }); + } + } + }; +}; + +export const removeWebContentsListeners = (id) => { + if (listeners[id]) { + listeners[id](); + } +}; + +export const addWebContentsEventListeners = (mmview, getServersFunction) => { + const contents = mmview.view.webContents; + + // initialize custom login tracking + customLogins[contents.id] = { + inProgress: false, + }; + + if (listeners[contents.id]) { + removeWebContentsListeners(contents.id); + } + const willNavigate = generateWillNavigate(getServersFunction); + contents.on('will-navigate', willNavigate); + + // handle custom login requests (oath, saml): + // 1. are we navigating to a supported local custom login path from the `/login` page? + // - indicate custom login is in progress + // 2. are we finished with the custom login process? + // - indicate custom login is NOT in progress + const didStartNavigation = generateDidStartNavigation(getServersFunction); + contents.on('did-start-navigation', didStartNavigation); + + const spellcheck = mmview.options.webPreferences.spellcheck; + const newWindow = generateNewWindowListener(getServersFunction, spellcheck); + contents.on('new-window', newWindow); + + contents.on('page-title-updated', mmview.handleTitleUpdate); + contents.on('page-favicon-updated', mmview.handleFaviconUpdate); + contents.on('update-target-url', mmview.handleUpdateTarget); + contents.on(FOUND_IN_PAGE, mmview.handleFoundInPage); + contents.on('did-navigate', mmview.handleDidNavigate); + + const removeListeners = () => { + try { + contents.removeListener('will-navigate', willNavigate); + contents.removeListener('did-start-navigation', didStartNavigation); + contents.removeListener('new-window', newWindow); + contents.removeListener('page-title-updated', mmview.handleTitleUpdate); + contents.removeListener('page-favicon-updated', mmview.handleFaviconUpdate); + contents.removeListener('update-target-url', mmview.handleUpdateTarget); + contents.removeListener(FOUND_IN_PAGE, mmview.handleFoundInPage); + contents.removeListener('did-navigate', mmview.handleDidNavigate); + } catch (e) { + log.error(`Error while trying to detach listeners, this might be ok if the process crashed: ${e}`); + } + }; + + listeners[contents.id] = removeListeners; + contents.once('render-process-gone', (event, details) => { + if (details !== 'clean-exit') { + log.error(`Renderer process for a webcontent is no longer available: ${details}`); + } + removeListeners(); + }); +}; diff --git a/src/main/windows/mainWindow.js b/src/main/windows/mainWindow.js new file mode 100644 index 00000000..f17cb392 --- /dev/null +++ b/src/main/windows/mainWindow.js @@ -0,0 +1,170 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import fs from 'fs'; + +import path from 'path'; +import os from 'os'; + +import {app, BrowserWindow} from 'electron'; +import log from 'electron-log'; + +import {SELECT_NEXT_TAB, SELECT_PREVIOUS_TAB} from 'common/communication'; + +import * as Validator from '../Validator'; +import contextMenu from '../contextMenu'; +import {getLocalURLString} from '../utils'; + +function saveWindowState(file, window) { + const windowState = window.getBounds(); + windowState.maximized = window.isMaximized(); + try { + fs.writeFileSync(file, JSON.stringify(windowState)); + } catch (e) { + // [Linux] error happens only when the window state is changed before the config dir is created. + log.error(e); + } +} + +function isFramelessWindow() { + return os.platform() === 'darwin' || (os.platform() === 'win32' && os.release().startsWith('10')); +} + +function createMainWindow(config, options) { + const defaultWindowWidth = 1000; + const defaultWindowHeight = 700; + const minimumWindowWidth = 400; + const minimumWindowHeight = 240; + + // Create the browser window. + const boundsInfoPath = path.join(app.getPath('userData'), 'bounds-info.json'); + let windowOptions; + try { + windowOptions = JSON.parse(fs.readFileSync(boundsInfoPath, 'utf-8')); + windowOptions = Validator.validateBoundsInfo(windowOptions); + if (!windowOptions) { + throw new Error('Provided bounds info file does not validate, using defaults instead.'); + } + } catch (e) { + // Follow Electron's defaults, except for window dimensions which targets 1024x768 screen resolution. + windowOptions = {width: defaultWindowWidth, height: defaultWindowHeight}; + } + + const {maximized: windowIsMaximized} = windowOptions; + + const spellcheck = (typeof config.useSpellChecker === 'undefined' ? true : config.useSpellChecker); + + if (process.platform === 'linux') { + windowOptions.icon = options.linuxAppIcon; + } + Object.assign(windowOptions, { + title: app.name, + fullscreenable: true, + show: false, // don't start the window until it is ready and only if it isn't hidden + paintWhenInitiallyHidden: true, // we want it to start painting to get info from the webapp + minWidth: minimumWindowWidth, + minHeight: minimumWindowHeight, + frame: !isFramelessWindow(), + fullscreen: false, + titleBarStyle: 'hidden', + trafficLightPosition: {x: 12, y: 24}, + backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + disableBlinkFeatures: 'Auxclick', + spellcheck, + enableRemoteModule: process.env.NODE_ENV === 'test', + }, + }); + + const mainWindow = new BrowserWindow(windowOptions); + mainWindow.setMenuBarVisibility(false); + + const localURL = getLocalURLString('index.html'); + mainWindow.loadURL(localURL).catch( + (reason) => { + log.error(`Main window failed to load: ${reason}`); + }); + mainWindow.once('ready-to-show', () => { + mainWindow.webContents.zoomLevel = 0; + + mainWindow.show(); + if (windowIsMaximized) { + mainWindow.maximize(); + } + }); + + mainWindow.once('show', () => { + mainWindow.show(); + }); + + mainWindow.once('restore', () => { + mainWindow.restore(); + }); + + // App should save bounds when a window is closed. + // However, 'close' is not fired in some situations(shutdown, ctrl+c) + // because main process is killed in such situations. + // 'blur' event was effective in order to avoid this. + // Ideally, app should detect that OS is shutting down. + mainWindow.on('blur', () => { + saveWindowState(boundsInfoPath, mainWindow); + }); + + mainWindow.on('close', (event) => { + if (global.willAppQuit) { // when [Ctrl|Cmd]+Q + saveWindowState(boundsInfoPath, mainWindow); + } else { // Minimize or hide the window for close button. + event.preventDefault(); + function hideWindow(window) { + window.blur(); // To move focus to the next top-level window in Windows + window.hide(); + } + switch (process.platform) { + case 'win32': + hideWindow(mainWindow); + break; + case 'linux': + if (config.minimizeToTray) { + hideWindow(mainWindow); + } else { + mainWindow.minimize(); + } + break; + case 'darwin': + // need to leave fullscreen first, then hide the window + if (mainWindow.isFullScreen()) { + mainWindow.once('leave-full-screen', () => { + app.hide(); + }); + mainWindow.setFullScreen(false); + } else { + app.hide(); + } + break; + default: + } + } + }); + + // Register keyboard shortcuts + mainWindow.webContents.on('before-input-event', (event, input) => { + // Add Alt+Cmd+(Right|Left) as alternative to switch between servers + if (process.platform === 'darwin') { + if (input.alt && input.meta) { + if (input.key === 'ArrowRight') { + mainWindow.webContents.send(SELECT_NEXT_TAB); + } + if (input.key === 'ArrowLeft') { + mainWindow.webContents.send(SELECT_PREVIOUS_TAB); + } + } + } + }); + + contextMenu.setup({useSpellChecker: config.useSpellChecker}); + return mainWindow; +} + +export default createMainWindow; diff --git a/src/main/windows/settingsWindow.js b/src/main/windows/settingsWindow.js new file mode 100644 index 00000000..7c384613 --- /dev/null +++ b/src/main/windows/settingsWindow.js @@ -0,0 +1,33 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {BrowserWindow} from 'electron'; +import log from 'electron-log'; + +import {getLocalURLString} from '../utils'; + +export function createSettingsWindow(mainWindow, config, withDevTools) { + const spellcheck = (typeof config.useSpellChecker === 'undefined' ? true : config.useSpellChecker); + const settingsWindow = new BrowserWindow({ + ...config.data, + parent: mainWindow, + title: 'Desktop App Settings', + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + spellcheck, + enableRemoteModule: process.env.NODE_ENV === 'test', + }}); + const localURL = getLocalURLString('settings.html'); + settingsWindow.loadURL(localURL).catch( + (reason) => { + log.error(`Settings window failed to load: ${reason}`); + log.info(process.env); + }); + settingsWindow.show(); + + if (withDevTools) { + settingsWindow.webContents.openDevTools({mode: 'detach'}); + } + return settingsWindow; +} diff --git a/src/main/windows/windowManager.js b/src/main/windows/windowManager.js new file mode 100644 index 00000000..c028fecd --- /dev/null +++ b/src/main/windows/windowManager.js @@ -0,0 +1,443 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import path from 'path'; +import {app, BrowserWindow, nativeImage, systemPreferences, ipcMain} from 'electron'; +import log from 'electron-log'; + +import {MAXIMIZE_CHANGE, FIND_IN_PAGE, STOP_FIND_IN_PAGE, CLOSE_FINDER, FOCUS_FINDER, HISTORY, GET_LOADING_SCREEN_DATA, REACT_APP_INITIALIZED, LOADING_SCREEN_ANIMATION_FINISHED, FOCUS_THREE_DOT_MENU} from 'common/communication'; +import urlUtils from 'common/utils/url'; + +import {getAdjustedWindowBoundaries} from '../utils'; + +import {ViewManager} from '../views/viewManager'; +import {CriticalErrorHandler} from '../CriticalErrorHandler'; + +import {createSettingsWindow} from './settingsWindow'; +import createMainWindow from './mainWindow'; + +// singleton module to manage application's windows + +const status = { + mainWindow: null, + settingsWindow: null, + config: null, + viewManager: null, +}; +const assetsDir = path.resolve(app.getAppPath(), 'assets'); + +ipcMain.on(FIND_IN_PAGE, findInPage); +ipcMain.on(STOP_FIND_IN_PAGE, stopFindInPage); +ipcMain.on(CLOSE_FINDER, closeFinder); +ipcMain.on(FOCUS_FINDER, focusFinder); +ipcMain.on(HISTORY, handleHistory); +ipcMain.handle(GET_LOADING_SCREEN_DATA, handleLoadingScreenDataRequest); +ipcMain.on(REACT_APP_INITIALIZED, handleReactAppInitialized); +ipcMain.on(LOADING_SCREEN_ANIMATION_FINISHED, handleLoadingScreenAnimationFinished); + +export function setConfig(data) { + if (data) { + status.config = data; + } + if (status.viewManager) { + status.viewManager.reloadConfiguration(status.config.teams); + } +} + +export function showSettingsWindow() { + if (status.settingsWindow) { + status.settingsWindow.show(); + } else { + if (!status.mainWindow) { + showMainWindow(); + } + const withDevTools = process.env.MM_DEBUG_SETTINGS || false; + + status.settingsWindow = createSettingsWindow(status.mainWindow, status.config, withDevTools); + status.settingsWindow.on('closed', () => { + status.settingsWindow = null; + focusBrowserView(); + }); + } +} + +export function showMainWindow(deeplinkingURL) { + if (status.mainWindow) { + status.mainWindow.show(); + } else { + status.mainWindow = createMainWindow(status.config, { + linuxAppIcon: path.join(assetsDir, 'appicon.png'), + }); + + if (!status.mainWindow) { + log.error('unable to create main window'); + app.quit(); + } + + // window handlers + status.mainWindow.on('closed', () => { + log.warn('main window closed'); + status.mainWindow = null; + }); + status.mainWindow.on('unresponsive', () => { + const criticalErrorHandler = new CriticalErrorHandler(); + criticalErrorHandler.setMainWindow(status.mainWindow); + criticalErrorHandler.windowUnresponsiveHandler(); + }); + status.mainWindow.on('crashed', handleMainWindowWebContentsCrashed); + status.mainWindow.on('maximize', handleMaximizeMainWindow); + status.mainWindow.on('unmaximize', handleUnmaximizeMainWindow); + status.mainWindow.on('resize', handleResizeMainWindow); + status.mainWindow.on('focus', focusBrowserView); + status.mainWindow.on('enter-full-screen', () => sendToRenderer('enter-full-screen')); + status.mainWindow.on('leave-full-screen', () => sendToRenderer('leave-full-screen')); + + if (process.env.MM_DEBUG_SETTINGS) { + status.mainWindow.webContents.openDevTools({mode: 'detach'}); + } + + if (status.viewManager) { + status.viewManager.updateMainWindow(status.mainWindow); + } + } + initializeViewManager(); + + if (deeplinkingURL) { + status.viewManager.handleDeepLink(deeplinkingURL); + } +} + +export function getMainWindow(ensureCreated) { + if (ensureCreated && status.mainWindow === null) { + showMainWindow(); + } + return status.mainWindow; +} + +export function on(event, listener) { + return status.mainWindow.on(event, listener); +} + +function handleMainWindowWebContentsCrashed() { + throw new Error('webContents \'crashed\' event has been emitted'); +} + +function handleMaximizeMainWindow() { + sendToRenderer(MAXIMIZE_CHANGE, true); +} + +function handleUnmaximizeMainWindow() { + sendToRenderer(MAXIMIZE_CHANGE, false); +} + +function handleResizeMainWindow(event, newBounds) { + setBoundsForCurrentView(event, newBounds); +} + +function setBoundsForCurrentView(event, newBounds) { + const currentView = status.viewManager.getCurrentView(); + const bounds = newBounds || status.mainWindow.getContentBounds(); + if (currentView) { + currentView.setBounds(getAdjustedWindowBoundaries(bounds.width, bounds.height, !urlUtils.isTeamUrl(currentView.server.url, currentView.view.webContents.getURL()))); + } + status.viewManager.setFinderBounds(); + status.viewManager.setLoadingScreenBounds(); +} + +export function sendToRenderer(channel, ...args) { + if (!status.mainWindow) { + showMainWindow(); + } + status.mainWindow.webContents.send(channel, ...args); + if (status.settingsWindow && status.settingsWindow.isVisible()) { + status.settingsWindow.webContents.send(channel, ...args); + } +} + +export function sendToAll(channel, ...args) { + sendToRenderer(channel, ...args); + if (status.settingsWindow) { + status.settingsWindow.webContents.send(channel, ...args); + } + + // TODO: should we include popups? +} + +export function sendToMattermostViews(channel, ...args) { + if (status.viewManager) { + status.viewManager.sendToAllViews(channel, ...args); + } +} + +export function restoreMain() { + log.info('restoreMain'); + if (!status.mainWindow) { + showMainWindow(); + } + if (!status.mainWindow.isVisible() || status.mainWindow.isMinimized()) { + if (status.mainWindow.isMinimized()) { + status.mainWindow.restore(); + } else { + status.mainWindow.show(); + } + if (status.settingsWindow) { + status.settingsWindow.focus(); + } else { + status.mainWindow.focus(); + } + if (process.platform === 'darwin') { + app.dock.show(); + } + } else if (status.settingsWindow) { + status.settingsWindow.focus(); + } else { + status.mainWindow.focus(); + } +} + +export function flashFrame(flash) { + if (process.platform === 'linux' || process.platform === 'win32') { + status.mainWindow.flashFrame(flash); + if (status.settingsWindow) { + // main might be hidden behind the settings + status.settingsWindow.flashFrame(flash); + } + } + if (process.platform === 'darwin' && status.config.notifications.bounceIcon) { + app.dock.bounce(status.config.notifications.bounceIconType); + } +} + +function drawBadge(text, small) { + const scale = 2; // should rely display dpi + const size = (small ? 20 : 16) * scale; + const canvas = document.createElement('canvas'); + canvas.setAttribute('width', size); + canvas.setAttribute('height', size); + const ctx = canvas.getContext('2d'); + + // circle + ctx.fillStyle = '#FF1744'; // Material Red A400 + ctx.beginPath(); + ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2); + ctx.fill(); + + // text + ctx.fillStyle = '#ffffff'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = (11 * scale) + 'px sans-serif'; + ctx.fillText(text, size / 2, size / 2, size); + + return canvas.toDataURL(); +} + +function createDataURL(text, small) { + const win = status.mainWindow; + if (!win) { + return null; + } + + // since we don't have a document/canvas object in the main process, we use the webcontents from the window to draw. + const safeSmall = Boolean(small); + const code = ` + window.drawBadge = ${drawBadge}; + window.drawBadge('${text || ''}', ${safeSmall}); + `; + return win.webContents.executeJavaScript(code); +} + +export async function setOverlayIcon(badgeText, description, small) { + if (process.platform === 'win32') { + let overlay = null; + if (status.mainWindow && badgeText) { + try { + const dataUrl = await createDataURL(badgeText, small); + overlay = nativeImage.createFromDataURL(dataUrl); + } catch (err) { + log.error(`Couldn't generate a badge: ${err}`); + } + } + status.mainWindow.setOverlayIcon(overlay, description); + } +} + +export function isMainWindow(window) { + return status.mainWindow && status.mainWindow === window; +} + +export function handleDoubleClick(e, windowType) { + let action = 'Maximize'; + if (process.platform === 'darwin') { + action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string'); + } + const win = (windowType === 'settings') ? status.settingsWindow : status.mainWindow; + switch (action) { + case 'Minimize': + if (win.isMinimized()) { + win.restore(); + } else { + win.minimize(); + } + break; + case 'Maximize': + default: + if (win.isMaximized()) { + win.unmaximize(); + } else { + win.maximize(); + } + break; + } +} + +function initializeViewManager() { + if (!status.viewManager) { + status.viewManager = new ViewManager(status.config, status.mainWindow); + status.viewManager.load(); + status.viewManager.showInitial(); + } +} + +export function switchServer(serverName) { + showMainWindow(); + status.viewManager.showByName(serverName); +} + +export function focusBrowserView() { + if (status.viewManager) { + status.viewManager.focus(); + } else { + log.error('Trying to call focus when the viewmanager has not yet been initialized'); + } +} + +export function openBrowserViewDevTools() { + if (status.viewManager) { + status.viewManager.openViewDevTools(); + } +} + +export function openFinder() { + if (status.viewManager) { + status.viewManager.showFinder(); + } +} + +export function closeFinder() { + if (status.viewManager) { + status.viewManager.hideFinder(); + } +} + +export function focusFinder() { + if (status.viewManager) { + status.viewManager.focusFinder(); + } +} + +export function foundInPage(result) { + if (status.viewManager && status.viewManager.foundInPage) { + status.viewManager.foundInPage(result); + } +} + +export function findInPage(event, searchText, options) { + if (status.viewManager) { + const activeView = status.viewManager.getCurrentView(); + if (activeView) { + activeView.view.webContents.findInPage(searchText, options); + } + } +} + +export function stopFindInPage(event, action) { + if (status.viewManager) { + const activeView = status.viewManager.getCurrentView(); + if (activeView) { + activeView.view.webContents.stopFindInPage(action); + } + } +} + +export function focusThreeDotMenu() { + if (status.mainWindow) { + status.mainWindow.webContents.focus(); + status.mainWindow.webContents.send(FOCUS_THREE_DOT_MENU); + } +} + +function handleLoadingScreenDataRequest() { + return { + darkMode: status.config.darkMode, + }; +} + +function handleReactAppInitialized(_, server) { + if (status.viewManager) { + status.viewManager.setServerInitialized(server); + } +} + +function handleLoadingScreenAnimationFinished() { + if (status.viewManager) { + status.viewManager.hideLoadingScreen(); + } +} + +export function updateLoadingScreenDarkMode(darkMode) { + if (status.viewManager) { + status.viewManager.updateLoadingScreenDarkMode(darkMode); + } +} + +export function getServerNameByWebContentsId(webContentsId) { + if (status.viewManager) { + return status.viewManager.findByWebContent(webContentsId); + } + return null; +} + +export function close() { + const focused = BrowserWindow.getFocusedWindow(); + if (focused.id === status.mainWindow.id) { + // TODO: figure out logic for closing + focused.close(); + } else { + focused.close(); + } +} +export function maximize() { + const focused = BrowserWindow.getFocusedWindow(); + focused.maximize(); +} +export function minimize() { + const focused = BrowserWindow.getFocusedWindow(); + focused.minimize(); +} +export function restore() { + const focused = BrowserWindow.getFocusedWindow(); + focused.restore(); +} + +export function reload() { + const currentView = status.viewManager.getCurrentView(); + if (currentView) { + status.viewManager.showLoadingScreen(); + currentView.reload(); + } +} + +export function handleHistory(event, offset) { + if (status.viewManager) { + const activeView = status.viewManager.getCurrentView(); + if (activeView && activeView.view.webContents.canGoToOffset(offset)) { + try { + activeView.view.webContents.goToOffset(offset); + } catch (error) { + log.error(error); + activeView.load(activeView.server.url); + } + } + } +} diff --git a/src/package-lock.json b/src/package-lock.json deleted file mode 100644 index 1c421b07..00000000 --- a/src/package-lock.json +++ /dev/null @@ -1,943 +0,0 @@ -{ - "name": "mattermost-desktop", - "version": "4.7.0-develop", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "7zip": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/7zip/-/7zip-0.0.6.tgz", - "integrity": "sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=" - }, - "@babel/runtime": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", - "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/runtime-corejs2": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.10.4.tgz", - "integrity": "sha512-9sArmpZDQsnR1yyAcU51DxQrntWxt0LUKjPp3pIyo7kVLfaqKt8muppcT87QmFkXV5H50qXAF8JWOjk0jaXRYA==", - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.4" - } - }, - "@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" - }, - "@hapi/formula": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", - "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==" - }, - "@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" - }, - "@hapi/joi": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz", - "integrity": "sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==", - "requires": { - "@hapi/address": "^2.1.2", - "@hapi/formula": "^1.2.0", - "@hapi/hoek": "^8.2.4", - "@hapi/pinpoint": "^1.0.2", - "@hapi/topo": "^3.1.3" - } - }, - "@hapi/pinpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", - "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==" - }, - "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "requires": { - "@hapi/hoek": "^8.3.0" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==" - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "applescript": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz", - "integrity": "sha1-u4evVoytA0pOSMS9r2Bno6JwExc=" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" - }, - "auto-launch": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/auto-launch/-/auto-launch-5.0.5.tgz", - "integrity": "sha512-ppdF4mihhYzMYLuCcx9H/c5TUOCev8uM7en53zWVQhyYAJrurd2bFZx3qQVeJKF2jrc7rsPRNN5cD+i23l6PdA==", - "requires": { - "applescript": "^1.0.0", - "mkdirp": "^0.5.1", - "path-is-absolute": "^1.0.0", - "untildify": "^3.0.2", - "winreg": "1.2.4" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "binarysearch": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/binarysearch/-/binarysearch-0.2.4.tgz", - "integrity": "sha1-Ru8+A/1FKekyhmLmjkAyjnoL8qw=" - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "bluebird-lst": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", - "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", - "requires": { - "bluebird": "^3.5.5" - } - }, - "bootstrap": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", - "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "builder-util-runtime": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.1.1.tgz", - "integrity": "sha512-+ieS4PMB33vVE2S3ZNWBEQJ1zKmAs/agrBdh7XadE1lKLjrH4aXYuOh9OOGdxqIRldhlhNBaF+yKMMEFOdNVig==", - "requires": { - "bluebird-lst": "^1.0.6", - "debug": "^4.1.1", - "fs-extra-p": "^7.0.0", - "sax": "^1.2.4" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - }, - "cross-unzip": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/cross-unzip/-/cross-unzip-0.0.2.tgz", - "integrity": "sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8=" - }, - "damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==" - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "requires": { - "@babel/runtime": "^7.1.2" - } - }, - "electron-context-menu": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-0.16.0.tgz", - "integrity": "sha512-lGr1/nRlNGmU8THc0hl2dYEB5bwXJpsi7vCjjsnsNGJKPZZLY8nHY3xvtjdtwKocErvp1h8wUb19moWenzoGPw==", - "requires": { - "cli-truncate": "^2.0.0", - "electron-dl": "^1.2.0", - "electron-is-dev": "^1.0.1" - } - }, - "electron-devtools-installer": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-2.2.4.tgz", - "integrity": "sha512-b5kcM3hmUqn64+RUcHjjr8ZMpHS2WJ5YO0pnG9+P/RTdx46of/JrEjuciHWux6pE+On6ynWhHJF53j/EDJN0PA==", - "requires": { - "7zip": "0.0.6", - "cross-unzip": "0.0.2", - "rimraf": "^2.5.2", - "semver": "^5.3.0" - } - }, - "electron-dl": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-1.14.0.tgz", - "integrity": "sha512-4okyei42a1mLsvLK7hLrIfd20EQzB18nIlLTwBV992aMSmTGLUEFRTmO1MfSslGNrzD8nuPuy1l/VxO8so4lig==", - "requires": { - "ext-name": "^5.0.0", - "pupa": "^1.0.0", - "unused-filename": "^1.0.0" - } - }, - "electron-is-dev": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz", - "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" - }, - "electron-log": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.2.2.tgz", - "integrity": "sha512-lBpLh1Q8qayrTxFIrTPcNjSHsosvUfOYyZ8glhiLcx7zCNPDGuj8+nXlEaaSS6LRiQQbLgLG+wKpuvztNzBIrA==" - }, - "electron-updater": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.0.6.tgz", - "integrity": "sha512-JPGLME6fxJcHG8hX7HWFl6Aew6iVm0DkcrENreKa5SUJCHG+uUaAhxDGDt+YGcNkyx1uJ6eBGMvFxDTLUv67pg==", - "requires": { - "bluebird-lst": "^1.0.6", - "builder-util-runtime": "~8.1.0", - "fs-extra-p": "^7.0.0", - "js-yaml": "^3.12.0", - "lazy-val": "^1.0.3", - "lodash.isequal": "^4.5.0", - "pako": "^1.0.7", - "semver": "^5.6.0", - "source-map-support": "^0.5.9" - } - }, - "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==" - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "ext-list": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", - "requires": { - "mime-db": "^1.28.0" - } - }, - "ext-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", - "requires": { - "ext-list": "^2.0.0", - "sort-keys-length": "^1.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "dependencies": { - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - } - } - }, - "font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-extra-p": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-7.0.1.tgz", - "integrity": "sha512-yhd2OV0HnHt2oitlp+X9hl2ReX4X/7kQeL7/72qzPHTZj5eUPGzAKOvEglU02Fa1OeG2rSy/aKB4WGVaLiF8tw==", - "requires": { - "bluebird-lst": "^1.0.7", - "fs-extra": "^7.0.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "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==" - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "keycode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", - "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" - }, - "lazy-val": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.4.tgz", - "integrity": "sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q==" - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "modify-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz", - "integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.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==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "prop-types-extra": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", - "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", - "requires": { - "react-is": "^16.3.2", - "warning": "^4.0.0" - }, - "dependencies": { - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, - "pupa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-1.0.0.tgz", - "integrity": "sha1-mpVopa9+ZXuEYqbp1TKHQ1YM7/Y=" - }, - "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-bootstrap": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.4.tgz", - "integrity": "sha512-xj+JfaPOvnvr3ow0aHC7Y3HaBKZNR1mm361hVxVzVX3fcdJNIrfiodbQ0m9nLBpNxiKG6FTU2lq/SbTDYT2vew==", - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "classnames": "^2.2.5", - "dom-helpers": "^3.2.0", - "invariant": "^2.2.4", - "keycode": "^2.2.0", - "prop-types": "^15.6.1", - "prop-types-extra": "^1.0.1", - "react-overlays": "^0.8.0", - "react-prop-types": "^0.4.0", - "react-transition-group": "^2.0.0", - "uncontrollable": "^5.0.0", - "warning": "^3.0.0" - } - }, - "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "react-overlays": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", - "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==", - "requires": { - "classnames": "^2.2.5", - "dom-helpers": "^3.2.1", - "prop-types": "^15.5.10", - "prop-types-extra": "^1.0.1", - "react-transition-group": "^2.2.0", - "warning": "^3.0.0" - } - }, - "react-prop-types": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", - "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", - "requires": { - "warning": "^3.0.0" - } - }, - "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "requires": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - } - }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "simple-spellchecker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-spellchecker/-/simple-spellchecker-1.0.1.tgz", - "integrity": "sha512-uYwrcM6kYR7g9psVdkKVap5fXJI1OSHWowsbowcUmH4VS178LcUCGS6E6/F9lsfRYVzFghJeF1zZL8W0GrBocA==", - "requires": { - "adm-zip": "^0.4.13", - "binarysearch": "^0.2.4", - "damerau-levenshtein": "^1.0.5", - "strip-bom": "^2.0.0", - "tmp": "^0.1.0" - } - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "sort-keys-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", - "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", - "requires": { - "sort-keys": "^1.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, - "tmp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", - "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", - "requires": { - "rimraf": "^2.6.3" - } - }, - "uncontrollable": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz", - "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==", - "requires": { - "invariant": "^2.2.4" - } - }, - "underscore": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", - "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==" - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==" - }, - "unused-filename": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-1.0.0.tgz", - "integrity": "sha1-00CID3GuIRXrqhMlvvBcxmhEacY=", - "requires": { - "modify-filename": "^1.1.0", - "path-exists": "^3.0.0" - } - }, - "valid-url": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", - "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" - }, - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "winreg": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", - "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" - }, - "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==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "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.1" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } -} diff --git a/src/package.json b/src/package.json deleted file mode 100644 index 3854ad6d..00000000 --- a/src/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "mattermost-desktop", - "productName": "Mattermost", - "desktopName": "Mattermost.desktop", - "version": "4.7.0-develop", - "description": "Mattermost", - "main": "main_bundle.js", - "author": "Mattermost, Inc. ", - "homepage": "https://about.mattermost.com", - "license": "Apache-2.0", - "dependencies": { - "@hapi/joi": "^16.1.8", - "auto-launch": "^5.0.5", - "bootstrap": "^3.3.7", - "classnames": "^2.2.6", - "electron-context-menu": "^0.16.0", - "electron-devtools-installer": "^2.2.4", - "electron-is-dev": "^1.0.1", - "electron-log": "^4.1.3", - "electron-updater": "4.0.6", - "font-awesome": "^4.7.0", - "prop-types": "^15.6.2", - "react": "^16.6.3", - "react-bootstrap": "~0.32.4", - "react-dom": "^16.6.3", - "react-transition-group": "^2.5.0", - "semver": "^5.5.0", - "simple-spellchecker": "^1.0.1", - "underscore": "^1.9.1", - "valid-url": "^1.0.9", - "winreg": "^1.2.4", - "yargs": "^15.3.1" - } -} diff --git a/src/renderer/assets/fonts/fontawesome-webfont.eot b/src/renderer/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/src/renderer/assets/fonts/fontawesome-webfont.eot differ diff --git a/src/renderer/assets/fonts/fontawesome-webfont.ttf b/src/renderer/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/src/renderer/assets/fonts/fontawesome-webfont.ttf differ diff --git a/src/renderer/assets/fonts/fontawesome-webfont.woff b/src/renderer/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/src/renderer/assets/fonts/fontawesome-webfont.woff differ diff --git a/src/renderer/assets/fonts/fontawesome-webfont.woff2 b/src/renderer/assets/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/src/renderer/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/src/renderer/assets/fonts/glyphicons-halflings-regular.eot b/src/renderer/assets/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 00000000..b93a4953 Binary files /dev/null and b/src/renderer/assets/fonts/glyphicons-halflings-regular.eot differ diff --git a/src/renderer/assets/fonts/glyphicons-halflings-regular.ttf b/src/renderer/assets/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 00000000..1413fc60 Binary files /dev/null and b/src/renderer/assets/fonts/glyphicons-halflings-regular.ttf differ diff --git a/src/renderer/assets/fonts/glyphicons-halflings-regular.woff b/src/renderer/assets/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 00000000..9e612858 Binary files /dev/null and b/src/renderer/assets/fonts/glyphicons-halflings-regular.woff differ diff --git a/src/renderer/assets/fonts/glyphicons-halflings-regular.woff2 b/src/renderer/assets/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 00000000..64539b54 Binary files /dev/null and b/src/renderer/assets/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2 b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2 new file mode 100644 index 00000000..9a71d1c7 Binary files /dev/null and b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2 differ diff --git a/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2 b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2 new file mode 100644 index 00000000..ec0bfee7 Binary files /dev/null and b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2 differ diff --git a/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2 b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2 new file mode 100644 index 00000000..d088697a Binary files /dev/null and b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2 differ diff --git a/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2 b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2 new file mode 100644 index 00000000..2d20d770 Binary files /dev/null and b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2 differ diff --git a/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2 b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2 new file mode 100644 index 00000000..9a96c63f Binary files /dev/null and b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2 differ diff --git a/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2 b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2 new file mode 100644 index 00000000..0964c7c4 Binary files /dev/null and b/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2 differ diff --git a/src/renderer/components/AutoSaveIndicator.jsx b/src/renderer/components/AutoSaveIndicator.jsx new file mode 100644 index 00000000..425c7928 --- /dev/null +++ b/src/renderer/components/AutoSaveIndicator.jsx @@ -0,0 +1,56 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Alert} from 'react-bootstrap'; + +const baseClassName = 'AutoSaveIndicator'; +const leaveClassName = `${baseClassName}-Leave`; + +const SAVING_STATE_SAVING = 'saving'; +const SAVING_STATE_SAVED = 'saved'; +const SAVING_STATE_ERROR = 'error'; +const SAVING_STATE_DONE = 'done'; + +function getClassNameAndMessage(savingState, errorMessage) { + switch (savingState) { + case SAVING_STATE_SAVING: + return {className: baseClassName, message: 'Saving...'}; + case SAVING_STATE_SAVED: + return {className: baseClassName, message: 'Saved'}; + case SAVING_STATE_ERROR: + return {className: `${baseClassName}`, message: errorMessage}; + case SAVING_STATE_DONE: + return {className: `${baseClassName} ${leaveClassName}`, message: 'Saved'}; + default: + return {className: `${baseClassName} ${leaveClassName}`, message: ''}; + } +} + +export default function AutoSaveIndicator(props) { + const {savingState, errorMessage, ...rest} = props; + const {className, message} = getClassNameAndMessage(savingState, errorMessage); + return ( + + {message} + + ); +} + +AutoSaveIndicator.propTypes = { + savingState: PropTypes.string.isRequired, + errorMessage: PropTypes.string, +}; + +Object.assign(AutoSaveIndicator, { + SAVING_STATE_SAVING, + SAVING_STATE_SAVED, + SAVING_STATE_ERROR, + SAVING_STATE_DONE, +}); diff --git a/src/renderer/components/Button/Button.stories.jsx b/src/renderer/components/Button/Button.stories.jsx new file mode 100644 index 00000000..ebeec782 --- /dev/null +++ b/src/renderer/components/Button/Button.stories.jsx @@ -0,0 +1,28 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import {storiesOf} from '@storybook/react'; + +import {action} from '@storybook/addon-actions'; +import {Button, ButtonToolbar} from 'react-bootstrap'; + +storiesOf('Button', module). + add('bsStyle', () => ( + + + + + + + )); diff --git a/src/renderer/components/DestructiveConfirmModal.jsx b/src/renderer/components/DestructiveConfirmModal.jsx new file mode 100644 index 00000000..fbe7e2eb --- /dev/null +++ b/src/renderer/components/DestructiveConfirmModal.jsx @@ -0,0 +1,45 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Button, Modal} from 'react-bootstrap'; + +export default function DestructiveConfirmationModal(props) { + const { + title, + body, + acceptLabel, + cancelLabel, + onAccept, + onCancel, + ...rest} = props; + return ( + + + {title} + + {body} + + + + + + ); +} + +DestructiveConfirmationModal.propTypes = { + title: PropTypes.string.isRequired, + body: PropTypes.node.isRequired, + acceptLabel: PropTypes.string.isRequired, + cancelLabel: PropTypes.string.isRequired, + onAccept: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; diff --git a/src/renderer/components/ErrorView.jsx b/src/renderer/components/ErrorView.jsx new file mode 100644 index 00000000..515f1576 --- /dev/null +++ b/src/renderer/components/ErrorView.jsx @@ -0,0 +1,88 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +// ErrorCode: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Grid, Row, Col} from 'react-bootstrap'; + +export default function ErrorView(props) { + const classNames = ['container', 'ErrorView']; + if (!props.active) { + classNames.push('ErrorView-hidden'); + } + + return ( + +
+ + ); +} + +ErrorView.propTypes = { + errorInfo: PropTypes.string, + url: PropTypes.string, + id: PropTypes.string, + active: PropTypes.bool, + appName: PropTypes.string, +}; diff --git a/src/renderer/components/ExtraBar.jsx b/src/renderer/components/ExtraBar.jsx new file mode 100644 index 00000000..50ed65c2 --- /dev/null +++ b/src/renderer/components/ExtraBar.jsx @@ -0,0 +1,50 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Row, Button} from 'react-bootstrap'; + +export default class ExtraBar extends React.PureComponent { + handleBack = () => { + if (this.props.goBack) { + this.props.goBack(); + } + } + render() { + let barClass = 'clear-mode'; + if (!this.props.show) { + barClass = 'hidden'; + } else if (this.props.darkMode) { + barClass = 'dark-mode'; + } + + return ( + +
+ +
+
+ ); + } +} + +ExtraBar.propTypes = { + darkMode: PropTypes.bool, + goBack: PropTypes.func, + show: PropTypes.bool, +}; diff --git a/src/renderer/components/LoadingAnimation/LoadingAnimation.jsx b/src/renderer/components/LoadingAnimation/LoadingAnimation.jsx new file mode 100644 index 00000000..33b588ad --- /dev/null +++ b/src/renderer/components/LoadingAnimation/LoadingAnimation.jsx @@ -0,0 +1,93 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; + +import useAnimationEnd from '../../hooks/useAnimationEnd.js'; + +import LoadingIcon from './LoadingIcon.jsx'; + +const LOADING_STATE = { + INITIALIZING: 'initializing', // animation graphics are hidden + LOADING: 'loading', // animation graphics fade in and animate + LOADED: 'loaded', // animation graphics fade out + COMPLETE: 'complete', // animation graphics are removed from the DOM +}; + +const ANIMATION_COMPLETION_DELAY = 500; + +/** + * A function component for rendering the animated MM logo loading sequence + * @param {boolean} loading - Prop that indicates whether currently loading or not + * @param {boolean} darkMode - Prop that indicates if dark mode is enabled + * @param {function} onLoadingAnimationComplete - Callback function to update when internal loading animation is complete + */ +function LoadingAnimation({ + loading = false, + darkMode = false, + onLoadAnimationComplete = null}, +) { + const loadingIconContainerRef = React.useRef(null); + const [animationState, setAnimationState] = React.useState(LOADING_STATE.INITIALIZING); + const [loadingAnimationComplete, setLoadingAnimationComplete] = React.useState(false); + + React.useEffect(() => { + if (loading) { + setAnimationState(LOADING_STATE.LOADING); + setLoadingAnimationComplete(false); + } + + // in order for the logo animation to fully complete before fading out, the LOADED state is not set until + // both the external loaded prop changes back to false and the internal loading animation is complete + if (!loading && loadingAnimationComplete) { + setAnimationState(LOADING_STATE.LOADED); + } + }, [loading]); + + React.useEffect(() => { + // in order for the logo animation to fully complete before fading out, the LOADED state is not set until + // both the external loaded prop goes back to false and the internal loading animation is complete + if (!loading && loadingAnimationComplete) { + setAnimationState(LOADING_STATE.LOADED); + } + }, [loadingAnimationComplete]); + + // listen for end of the css logo animation sequence + useAnimationEnd(loadingIconContainerRef, () => { + setTimeout(() => { + setLoadingAnimationComplete(true); + }, ANIMATION_COMPLETION_DELAY); + }, 'LoadingAnimation__compass-shrink'); + + // listen for end of final css logo fade/shrink animation sequence + useAnimationEnd(loadingIconContainerRef, () => { + if (onLoadAnimationComplete) { + onLoadAnimationComplete(); + } + setAnimationState(LOADING_STATE.COMPLETE); + }, 'LoadingAnimation__shrink'); + + return ( +
+ +
+ ); +} + +LoadingAnimation.propTypes = { + loading: PropTypes.bool, + darkMode: PropTypes.bool, + onLoadAnimationComplete: PropTypes.func, +}; + +export default LoadingAnimation; diff --git a/src/renderer/components/LoadingAnimation/LoadingIcon.jsx b/src/renderer/components/LoadingAnimation/LoadingIcon.jsx new file mode 100644 index 00000000..cb429efd --- /dev/null +++ b/src/renderer/components/LoadingAnimation/LoadingIcon.jsx @@ -0,0 +1,197 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +/** + * A function component for inlining SVG code for animation logo loader + */ +function LoadingAnimation() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +export default LoadingAnimation; diff --git a/src/browser/components/LoadingAnimation/index.js b/src/renderer/components/LoadingAnimation/index.js similarity index 100% rename from src/browser/components/LoadingAnimation/index.js rename to src/renderer/components/LoadingAnimation/index.js diff --git a/src/renderer/components/LoadingScreen.jsx b/src/renderer/components/LoadingScreen.jsx new file mode 100644 index 00000000..256eb732 --- /dev/null +++ b/src/renderer/components/LoadingScreen.jsx @@ -0,0 +1,78 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; + +import useTransitionEnd from '../hooks/useTransitionEnd.js'; + +import LoadingAnimation from './LoadingAnimation'; + +/** + * A function component for rendering the desktop app loading screen + * @param {boolean} loading - Prop that indicates whether currently loading or not + * @param {boolean} darkMode - Prop that indicates if dark mode is enabled + * @param {() => void} onFadeOutComplete - Function to call when the loading animation is completely finished + */ +function LoadingScreen({loading = false, darkMode = false, onFadeOutComplete = () => null}) { + const loadingScreenRef = React.useRef(null); + + const [loadingIsComplete, setLoadingIsComplete] = React.useState(true); + const [loadAnimationIsComplete, setLoadAnimationIsComplete] = React.useState(true); + const [fadeOutIsComplete, setFadeOutIsComplete] = React.useState(true); + + React.useEffect(() => { + // reset internal state if loading restarts + if (loading) { + resetState(); + } else { + setLoadingIsComplete(true); + } + }, [loading]); + + function handleLoadAnimationComplete() { + setLoadAnimationIsComplete(true); + } + + useTransitionEnd(loadingScreenRef, React.useCallback(() => { + setFadeOutIsComplete(true); + onFadeOutComplete(); + }), ['opacity']); + + function loadingInProgress() { + return !(loadingIsComplete && loadAnimationIsComplete && fadeOutIsComplete); + } + + function resetState() { + setLoadingIsComplete(false); + setLoadAnimationIsComplete(false); + setFadeOutIsComplete(false); + } + + const loadingScreen = ( +
+ +
+ ); + + return loadingInProgress() ? loadingScreen : null; +} + +LoadingScreen.propTypes = { + loading: PropTypes.bool, + darkMode: PropTypes.bool, + onFadeOutComplete: PropTypes.func, +}; + +export default LoadingScreen; diff --git a/src/renderer/components/MainPage.jsx b/src/renderer/components/MainPage.jsx new file mode 100644 index 00000000..320cdce7 --- /dev/null +++ b/src/renderer/components/MainPage.jsx @@ -0,0 +1,453 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import os from 'os'; + +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import {Grid, Row} from 'react-bootstrap'; +import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon'; + +import {ipcRenderer} from 'electron'; + +import { + FOCUS_BROWSERVIEW, + MAXIMIZE_CHANGE, + DARK_MODE_CHANGE, + HISTORY, + LOAD_RETRY, + LOAD_SUCCESS, + LOAD_FAILED, + SHOW_NEW_SERVER_MODAL, + SWITCH_SERVER, + WINDOW_CLOSE, + WINDOW_MINIMIZE, + WINDOW_RESTORE, + WINDOW_MAXIMIZE, + DOUBLE_CLICK_ON_WINDOW, + PLAY_SOUND, + MODAL_OPEN, + MODAL_CLOSE, + SET_SERVER_KEY, + UPDATE_MENTIONS, + TOGGLE_BACK_BUTTON, + SELECT_NEXT_TAB, + SELECT_PREVIOUS_TAB, + ADD_SERVER, + FOCUS_THREE_DOT_MENU, +} from 'common/communication'; + +import restoreButton from '../../assets/titlebar/chrome-restore.svg'; +import maximizeButton from '../../assets/titlebar/chrome-maximize.svg'; +import minimizeButton from '../../assets/titlebar/chrome-minimize.svg'; +import closeButton from '../../assets/titlebar/chrome-close.svg'; + +import {playSound} from '../notificationSounds'; + +import TabBar from './TabBar.jsx'; +import ExtraBar from './ExtraBar.jsx'; +import ErrorView from './ErrorView.jsx'; + +const LOADING = 1; +const DONE = 2; +const RETRY = -1; +const FAILED = 0; +const NOSERVERS = -2; + +export default class MainPage extends React.PureComponent { + constructor(props) { + super(props); + + this.topBar = React.createRef(); + this.threeDotMenu = React.createRef(); + + this.state = { + key: this.props.teams.findIndex((team) => team.order === 0), + sessionsExpired: {}, + unreadCounts: {}, + mentionCounts: {}, + targetURL: '', + maximized: false, + tabStatus: new Map(this.props.teams.map((server) => [server.name, {status: LOADING, extra: null}])), + darkMode: this.props.darkMode, + }; + } + + getTabStatus() { + if (this.props.teams.length) { + const tab = this.props.teams[this.state.key]; + if (tab) { + const tabname = tab.name; + return this.state.tabStatus.get(tabname); + } + } + return {status: NOSERVERS}; + } + + componentDidMount() { + // set page on retry + ipcRenderer.on(LOAD_RETRY, (_, server, retry, err, loadUrl) => { + console.log(`${server}: failed to load ${err}, but retrying`); + const status = this.state.tabStatus; + const statusValue = { + status: RETRY, + extra: { + retry, + error: err, + url: loadUrl, + }, + }; + status.set(server, statusValue); + this.setState({tabStatus: status}); + }); + + ipcRenderer.on(LOAD_SUCCESS, (_, server) => { + const status = this.state.tabStatus; + status.set(server, {status: DONE}); + this.setState({tabStatus: status}); + }); + + ipcRenderer.on(LOAD_FAILED, (_, server, err, loadUrl) => { + console.log(`${server}: failed to load ${err}`); + const status = this.state.tabStatus; + const statusValue = { + status: FAILED, + extra: { + error: err, + url: loadUrl, + }, + }; + status.set(server, statusValue); + this.setState({tabStatus: status}); + }); + + ipcRenderer.on(DARK_MODE_CHANGE, (_, darkMode) => { + this.setState({darkMode}); + }); + + // can't switch tabs sequentially for some reason... + ipcRenderer.on(SET_SERVER_KEY, (event, key) => { + const nextIndex = this.props.teams.findIndex((team) => team.order === key); + this.handleSetServerKey(nextIndex); + }); + ipcRenderer.on(SELECT_NEXT_TAB, () => { + const currentOrder = this.props.teams[this.state.key].order; + const nextOrder = ((currentOrder + 1) % this.props.teams.length); + const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder); + const team = this.props.teams[nextIndex]; + this.handleSelect(team.name, nextIndex); + }); + + ipcRenderer.on(SELECT_PREVIOUS_TAB, () => { + const currentOrder = this.props.teams[this.state.key].order; + + // js modulo operator returns a negative number if result is negative, so we have to ensure it's positive + const nextOrder = ((this.props.teams.length + (currentOrder - 1)) % this.props.teams.length); + const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder); + const team = this.props.teams[nextIndex]; + this.handleSelect(team.name, nextIndex); + }); + + ipcRenderer.on(MAXIMIZE_CHANGE, this.handleMaximizeState); + + ipcRenderer.on('enter-full-screen', () => this.handleFullScreenState(true)); + ipcRenderer.on('leave-full-screen', () => this.handleFullScreenState(false)); + + ipcRenderer.on(ADD_SERVER, () => { + this.addServer(); + }); + + ipcRenderer.on(PLAY_SOUND, (_event, soundName) => { + playSound(soundName); + }); + + ipcRenderer.on(MODAL_OPEN, () => { + this.setState({modalOpen: true}); + }); + + ipcRenderer.on(MODAL_CLOSE, () => { + this.setState({modalOpen: false}); + }); + + ipcRenderer.on(TOGGLE_BACK_BUTTON, (event, showExtraBar) => { + this.setState({showExtraBar}); + }); + + ipcRenderer.on(UPDATE_MENTIONS, (_event, team, mentions, unreads, isExpired) => { + const key = this.props.teams.findIndex((server) => server.name === team); + const {unreadCounts, mentionCounts, sessionsExpired} = this.state; + + const newMentionCounts = {...mentionCounts}; + newMentionCounts[key] = mentions || 0; + + const newUnreads = {...unreadCounts}; + newUnreads[key] = unreads || false; + + const expired = {...sessionsExpired}; + expired[key] = isExpired || false; + + this.setState({unreadCounts: newUnreads, mentionCounts: newMentionCounts, sessionsExpired: expired}); + }); + + if (process.platform !== 'darwin') { + ipcRenderer.on(FOCUS_THREE_DOT_MENU, () => { + if (this.threeDotMenu.current) { + this.threeDotMenu.current.focus(); + } + }); + } + } + + handleMaximizeState = (_, maximized) => { + this.setState({maximized}); + } + + handleFullScreenState = (isFullScreen) => { + this.setState({fullScreen: isFullScreen}); + } + + handleSetServerKey = (key) => { + const newKey = (this.props.teams.length + key) % this.props.teams.length; + this.setState({key: newKey}); + } + + handleSelect = (name, key) => { + ipcRenderer.send(SWITCH_SERVER, name); + this.handleSetServerKey(key); + } + + handleDragAndDrop = async (dropResult) => { + const {removedIndex, addedIndex} = dropResult; + if (removedIndex !== addedIndex) { + const teamIndex = await this.props.moveTabs(removedIndex, addedIndex < this.props.teams.length ? addedIndex : this.props.teams.length - 1); + const name = this.props.teams[teamIndex].name; + this.handleSelect(name, teamIndex); + } + } + + handleClose = (e) => { + e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back. + ipcRenderer.send(WINDOW_CLOSE); + } + + handleMinimize = (e) => { + e.stopPropagation(); + ipcRenderer.send(WINDOW_MINIMIZE); + } + + handleMaximize = (e) => { + e.stopPropagation(); + ipcRenderer.send(WINDOW_MAXIMIZE); + } + + handleRestore = () => { + ipcRenderer.send(WINDOW_RESTORE); + } + + openMenu = () => { + if (process.platform !== 'darwin') { + this.threeDotMenu.current.blur(); + } + this.props.openMenu(); + } + + handleDoubleClick = () => { + ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW); + } + + addServer = () => { + ipcRenderer.send(SHOW_NEW_SERVER_MODAL); + } + + focusOnWebView = () => { + ipcRenderer.send(FOCUS_BROWSERVIEW); + } + + setInputRef = (ref) => { + this.inputRef = ref; + } + + render() { + const tabsRow = ( + + ); + + let topBarClassName = 'topBar'; + if (process.platform === 'darwin') { + topBarClassName += ' macOS'; + } + if (this.state.darkMode) { + topBarClassName += ' darkMode'; + } + if (this.state.fullScreen) { + topBarClassName += ' fullScreen'; + } + + let maxButton; + if (this.state.maximized) { + maxButton = ( +
+ +
+ ); + } else { + maxButton = ( +
+ +
+ ); + } + + let overlayGradient; + if (process.platform !== 'darwin') { + overlayGradient = ( + + ); + } + + let titleBarButtons; + if (os.platform() === 'win32' && os.release().startsWith('10')) { + titleBarButtons = ( + +
+ +
+ {maxButton} +
+ +
+
+ ); + } + + const topRow = ( + +
+ + {tabsRow} + {overlayGradient} + {titleBarButtons} +
+
+ ); + + const views = () => { + let component; + const tabStatus = this.getTabStatus(); + if (!tabStatus) { + const tab = this.props.teams[this.state.key]; + if (tab) { + console.error(`Not tabStatus for ${this.props.teams[this.state.key].name}`); + } else { + console.error('No tab status, tab doesn\'t exist anymore'); + } + return null; + } + switch (tabStatus.status) { + case NOSERVERS: // TODO: substitute with https://mattermost.atlassian.net/browse/MM-25003 + component = ( + ); + break; + case FAILED: + component = ( + ); + break; + case LOADING: + case RETRY: + case DONE: + component = null; + } + return component; + }; + + const viewsRow = ( + + { + ipcRenderer.send(HISTORY, -1); + }} + /> + + {views()} + + ); + + return ( +
+ + {topRow} + {viewsRow} + +
+ ); + } +} + +MainPage.propTypes = { + teams: PropTypes.array.isRequired, + showAddServerButton: PropTypes.bool.isRequired, + moveTabs: PropTypes.func.isRequired, + openMenu: PropTypes.func.isRequired, + darkMode: PropTypes.bool.isRequired, + appName: PropTypes.string.isRequired, +}; diff --git a/src/renderer/components/NewTeamModal.jsx b/src/renderer/components/NewTeamModal.jsx new file mode 100644 index 00000000..975ca771 --- /dev/null +++ b/src/renderer/components/NewTeamModal.jsx @@ -0,0 +1,244 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Modal, Button, FormGroup, FormControl, ControlLabel, HelpBlock} from 'react-bootstrap'; + +import urlUtils from 'common/utils/url'; + +export default class NewTeamModal extends React.PureComponent { + static defaultProps = { + restoreFocus: true, + }; + + constructor(props) { + super(props); + + this.wasShown = false; + this.state = { + teamName: '', + teamUrl: '', + teamOrder: props.currentOrder || 0, + saveStarted: false, + }; + } + + initializeOnShow() { + this.setState({ + teamName: this.props.team ? this.props.team.name : '', + teamUrl: this.props.team ? this.props.team.url : '', + teamIndex: this.props.team ? this.props.team.index : false, + teamOrder: this.props.team ? this.props.team.order : (this.props.currentOrder || 0), + saveStarted: false, + }); + } + + getTeamNameValidationError() { + if (!this.state.saveStarted) { + return null; + } + return this.state.teamName.length > 0 ? null : 'Name is required.'; + } + + getTeamNameValidationState() { + return this.getTeamNameValidationError() === null ? null : 'error'; + } + + handleTeamNameChange = (e) => { + this.setState({ + teamName: e.target.value, + }); + } + + getTeamUrlValidationError() { + if (!this.state.saveStarted) { + return null; + } + if (this.state.teamUrl.length === 0) { + return 'URL is required.'; + } + if (!(/^https?:\/\/.*/).test(this.state.teamUrl.trim())) { + return 'URL should start with http:// or https://.'; + } + if (!urlUtils.isValidURL(this.state.teamUrl.trim())) { + return 'URL is not formatted correctly.'; + } + return null; + } + + getTeamUrlValidationState() { + return this.getTeamUrlValidationError() === null ? null : 'error'; + } + + handleTeamUrlChange = (e) => { + this.setState({ + teamUrl: e.target.value, + }); + } + + getError() { + const nameError = this.getTeamNameValidationError(); + const urlError = this.getTeamUrlValidationError(); + + if (nameError && urlError) { + return 'Name and URL are required.'; + } else if (nameError) { + return nameError; + } else if (urlError) { + return urlError; + } + return null; + } + + validateForm() { + return this.getTeamNameValidationState() === null && + this.getTeamUrlValidationState() === null; + } + + save = () => { + this.setState({ + saveStarted: true, + }, () => { + if (this.validateForm()) { + this.props.onSave({ + url: this.state.teamUrl, + name: this.state.teamName, + index: this.state.teamIndex, + order: this.state.teamOrder, + }); + } + }); + } + + getSaveButtonLabel() { + if (this.props.editMode) { + return 'Save'; + } + return 'Add'; + } + + getModalTitle() { + if (this.props.editMode) { + return 'Edit Server'; + } + return 'Add Server'; + } + + render() { + if (this.wasShown !== this.props.show && this.props.show) { + this.initializeOnShow(); + } + this.wasShown = this.props.show; + + return ( + this.teamNameInputRef.focus()} + onHide={this.props.onClose} + restoreFocus={this.props.restoreFocus} + onKeyDown={(e) => { + switch (e.key) { + case 'Enter': + this.save(); + + // The add button from behind this might still be focused + e.preventDefault(); + e.stopPropagation(); + break; + case 'Escape': + this.props.onClose(); + break; + } + }} + > + + {this.getModalTitle()} + + + +
+ + {'Server Display Name'} + { + this.teamNameInputRef = ref; + if (this.props.setInputRef) { + this.props.setInputRef(ref); + } + }} + onClick={(e) => { + e.stopPropagation(); + }} + autoFocus={true} + /> + + {'The name of the server displayed on your desktop app tab bar.'} + + + {'Server URL'} + { + e.stopPropagation(); + }} + /> + + {'The URL of your Mattermost server. Must start with http:// or https://.'} + +
+
+ + +
+ {this.getError()} +
+ + + +
+ +
+ ); + } +} + +NewTeamModal.propTypes = { + onClose: PropTypes.func, + onSave: PropTypes.func, + team: PropTypes.object, + editMode: PropTypes.bool, + show: PropTypes.bool, + restoreFocus: PropTypes.bool, + currentOrder: PropTypes.number, + setInputRef: PropTypes.func, +}; diff --git a/src/renderer/components/RemoveServerModal.jsx b/src/renderer/components/RemoveServerModal.jsx new file mode 100644 index 00000000..9c80c237 --- /dev/null +++ b/src/renderer/components/RemoveServerModal.jsx @@ -0,0 +1,36 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Modal} from 'react-bootstrap'; + +import DestructiveConfirmationModal from './DestructiveConfirmModal.jsx'; + +export default function RemoveServerModal(props) { + const {serverName, ...rest} = props; + return ( + +

+ {'This will remove the server from your Desktop App but will not delete any of its data' + + ' - you can add the server back to the app at any time.'} +

+

+ {'Confirm you wish to remove the '}{serverName}{' server?'} +

+ + )} + /> + ); +} + +RemoveServerModal.propTypes = { + serverName: PropTypes.string.isRequired, +}; diff --git a/src/renderer/components/SettingsPage.jsx b/src/renderer/components/SettingsPage.jsx new file mode 100644 index 00000000..48507cc6 --- /dev/null +++ b/src/renderer/components/SettingsPage.jsx @@ -0,0 +1,723 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import 'renderer/css/settings.css'; + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row, Button} from 'react-bootstrap'; + +import {ipcRenderer} from 'electron'; +import {debounce} from 'underscore'; + +import {GET_LOCAL_CONFIGURATION, UPDATE_CONFIGURATION, DOUBLE_CLICK_ON_WINDOW, GET_DOWNLOAD_LOCATION, SWITCH_SERVER, ADD_SERVER, RELOAD_CONFIGURATION} from 'common/communication'; + +import TeamList from './TeamList.jsx'; +import AutoSaveIndicator from './AutoSaveIndicator.jsx'; + +const CONFIG_TYPE_SERVERS = 'servers'; +const CONFIG_TYPE_APP_OPTIONS = 'appOptions'; + +function backToIndex(serverName) { + ipcRenderer.send(SWITCH_SERVER, serverName); + window.close(); +} + +export default class SettingsPage extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + ready: false, + teams: [], + showAddTeamForm: false, + savingState: { + appOptions: AutoSaveIndicator.SAVING_STATE_DONE, + servers: AutoSaveIndicator.SAVING_STATE_DONE, + }, + userOpenedDownloadDialog: false, + }; + + this.getConfig(); + this.trayIconThemeRef = React.createRef(); + this.downloadLocationRef = React.createRef(); + this.showTrayIconRef = React.createRef(); + this.autostartRef = React.createRef(); + this.minimizeToTrayRef = React.createRef(); + this.flashWindowRef = React.createRef(); + this.bounceIconRef = React.createRef(); + this.showUnreadBadgeRef = React.createRef(); + this.useSpellCheckerRef = React.createRef(); + this.enableHardwareAccelerationRef = React.createRef(); + + this.saveQueue = []; + } + + componentDidMount() { + ipcRenderer.on(ADD_SERVER, () => { + this.setState({ + showAddTeamForm: true, + }); + }); + + ipcRenderer.on(RELOAD_CONFIGURATION, () => { + this.updateSaveState(); + this.getConfig(); + }); + } + + getConfig = () => { + ipcRenderer.invoke(GET_LOCAL_CONFIGURATION).then((config) => { + this.setState({ready: true, maximized: false, ...this.convertConfigDataToState(config)}); + }); + } + + convertConfigDataToState = (configData, currentState = {}) => { + const newState = Object.assign({}, configData); + newState.showAddTeamForm = currentState.showAddTeamForm || false; + newState.trayWasVisible = currentState.trayWasVisible || false; + if (newState.teams.length === 0 && currentState.firstRun !== false) { + newState.firstRun = false; + newState.showAddTeamForm = true; + } + newState.savingState = currentState.savingState || { + appOptions: AutoSaveIndicator.SAVING_STATE_DONE, + servers: AutoSaveIndicator.SAVING_STATE_DONE, + }; + return newState; + } + + saveSetting = (configType, {key, data}) => { + this.saveQueue.push({ + configType, + key, + data, + }); + this.updateSaveState(); + this.processSaveQueue(); + } + + processSaveQueue = debounce(() => { + ipcRenderer.send(UPDATE_CONFIGURATION, this.saveQueue.splice(0, this.saveQueue.length)); + }, 500); + + updateSaveState = () => { + let queuedUpdateCounts = { + [CONFIG_TYPE_SERVERS]: 0, + [CONFIG_TYPE_APP_OPTIONS]: 0, + }; + + queuedUpdateCounts = this.saveQueue.reduce((updateCounts, {configType}) => { + updateCounts[configType]++; + return updateCounts; + }, queuedUpdateCounts); + + const savingState = Object.assign({}, this.state.savingState); + + Object.entries(queuedUpdateCounts).forEach(([configType, count]) => { + if (count > 0) { + savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVING; + } else if (count === 0 && savingState[configType] === AutoSaveIndicator.SAVING_STATE_SAVING) { + savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVED; + this.resetSaveState(configType); + } + }); + + this.setState({savingState}); + } + + resetSaveState = debounce((configType) => { + if (this.state.savingState[configType] !== AutoSaveIndicator.SAVING_STATE_SAVING) { + const savingState = Object.assign({}, this.state.savingState); + savingState[configType] = AutoSaveIndicator.SAVING_STATE_DONE; + this.setState({savingState}); + } + }, 2000); + + handleTeamsChange = (teams) => { + setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); + this.setState({ + showAddTeamForm: false, + teams, + }); + if (teams.length === 0) { + this.setState({showAddTeamForm: true}); + } + } + + handleChangeShowTrayIcon = () => { + const shouldShowTrayIcon = !this.showTrayIconRef.current.props.checked; + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon}); + this.setState({ + showTrayIcon: shouldShowTrayIcon, + }); + + if (process.platform === 'darwin' && !shouldShowTrayIcon) { + this.setState({ + minimizeToTray: false, + }); + } + } + + handleChangeTrayIconTheme = (theme) => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'trayIconTheme', data: theme}); + this.setState({ + trayIconTheme: theme, + }); + } + + handleChangeAutoStart = () => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: !this.autostartRef.current.props.checked}); + this.setState({ + autostart: !this.autostartRef.current.props.checked, + }); + } + + handleChangeMinimizeToTray = () => { + const shouldMinimizeToTray = this.state.showTrayIcon && !this.minimizeToTrayRef.current.props.checked; + + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray}); + this.setState({ + minimizeToTray: shouldMinimizeToTray, + }); + } + + toggleShowTeamForm = () => { + this.setState({ + showAddTeamForm: !this.state.showAddTeamForm, + }); + document.activeElement.blur(); + } + + setShowTeamFormVisibility = (val) => { + this.setState({ + showAddTeamForm: val, + }); + } + + handleFlashWindow = () => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { + key: 'notifications', + data: { + ...this.state.notifications, + flashWindow: this.flashWindowRef.current.props.checked ? 0 : 2, + }, + }); + this.setState({ + notifications: { + ...this.state.notifications, + flashWindow: this.flashWindowRef.current.props.checked ? 0 : 2, + }, + }); + } + + handleBounceIcon = () => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { + key: 'notifications', + data: { + ...this.state.notifications, + bounceIcon: !this.bounceIconRef.current.props.checked, + }, + }); + this.setState({ + notifications: { + ...this.state.notifications, + bounceIcon: !this.bounceIconRef.current.props.checked, + }, + }); + } + + handleBounceIconType = (event) => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, { + key: 'notifications', + data: { + ...this.state.notifications, + bounceIconType: event.target.value, + }, + }); + this.setState({ + notifications: { + ...this.state.notifications, + bounceIconType: event.target.value, + }, + }); + } + + handleShowUnreadBadge = () => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: !this.showUnreadBadgeRef.current.props.checked}); + this.setState({ + showUnreadBadge: !this.showUnreadBadgeRef.current.props.checked, + }); + } + + handleChangeUseSpellChecker = () => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: !this.useSpellCheckerRef.current.props.checked}); + this.setState({ + useSpellChecker: !this.useSpellCheckerRef.current.props.checked, + }); + } + + handleChangeEnableHardwareAcceleration = () => { + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: !this.enableHardwareAccelerationRef.current.props.checked}); + this.setState({ + enableHardwareAcceleration: !this.enableHardwareAccelerationRef.current.props.checked, + }); + } + + saveDownloadLocation = (location) => { + this.setState({ + downloadLocation: location, + }); + setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'downloadLocation', data: location}); + } + + handleChangeDownloadLocation = (e) => { + this.saveDownloadLocation(e.target.value); + } + + selectDownloadLocation = () => { + if (!this.state.userOpenedDownloadDialog) { + ipcRenderer.invoke(GET_DOWNLOAD_LOCATION, `/Users/${process.env.USER || process.env.USERNAME}/Downloads`).then((result) => this.saveDownloadLocation(result)); + this.setState({userOpenedDownloadDialog: true}); + } + this.setState({userOpenedDownloadDialog: false}); + } + + updateTeam = (index, newData) => { + const teams = this.state.teams; + teams[index] = newData; + setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); + this.setState({ + teams, + }); + } + + addServer = (team) => { + const teams = this.state.teams; + teams.push(team); + setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams}); + this.setState({ + teams, + }); + } + + openMenu = () => { + // @eslint-ignore + this.threeDotMenu.current.blur(); + this.props.openMenu(); + } + + handleDoubleClick = () => { + ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, 'settings'); + } + + render() { + const settingsPage = { + navbar: { + backgroundColor: '#fff', + position: 'relative', + }, + close: { + textDecoration: 'none', + position: 'absolute', + right: '0', + top: '5px', + fontSize: '35px', + fontWeight: 'normal', + color: '#bbb', + }, + heading: { + textAlign: 'center', + fontSize: '24px', + margin: '0', + padding: '1em 0', + }, + sectionHeading: { + fontSize: '20px', + margin: '0', + padding: '1em 0', + float: 'left', + }, + sectionHeadingLink: { + marginTop: '24px', + display: 'inline-block', + fontSize: '15px', + }, + footer: { + padding: '0.4em 0', + }, + downloadLocationInput: { + marginRight: '3px', + marginTop: '8px', + width: '320px', + height: '34px', + padding: '0 12px', + borderRadius: '4px', + border: '1px solid #ccc', + fontWeight: '500', + }, + + downloadLocationButton: { + marginBottom: '4px', + }, + + container: { + paddingBottom: '40px', + }, + }; + + const teamsRow = ( + + + { + backToIndex(name); + }} + /> + + + ); + + const serversRow = ( + + +

{'Server Management'}

+
+ +
+ + +

+ {'+ Add New Server'} +

+ +
+ ); + + let srvMgmt; + if (this.state.enableServerManagement === true) { + srvMgmt = ( +
+ {serversRow} + {teamsRow} +
+
+ ); + } + + const options = []; + + // MacOS has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX + if (process.platform === 'win32' || process.platform === 'linux') { + options.push( + + {'Start app on login'} + + {'If enabled, the app starts automatically when you log in to your machine.'} + + ); + } + + options.push( + + {'Check spelling'} + + {'Highlight misspelled words in your messages.'} + {' Available for English, French, German, Portuguese, Spanish, and Dutch.'} + + ); + + if (process.platform === 'darwin' || process.platform === 'win32') { + const TASKBAR = process.platform === 'win32' ? 'taskbar' : 'Dock'; + options.push( + + {`Show red badge on ${TASKBAR} icon to indicate unread messages`} + + {`Regardless of this setting, mentions are always indicated with a red badge and item count on the ${TASKBAR} icon.`} + + ); + } + + if (process.platform === 'win32' || process.platform === 'linux') { + options.push( + + {'Flash app window and taskbar icon when a new message is received'} + + {'If enabled, app window and taskbar icon flash for a few seconds when a new message is received.'} + + ); + } + + if (process.platform === 'darwin') { + options.push( + + + {'Bounce the Dock icon'} + + + {'once'} + + {' '} + + {'until I open the app'} + + + {'If enabled, the Dock icon bounces once or until the user opens the app when a new notification is received.'} + + , + ); + } + + if (process.platform === 'darwin' || process.platform === 'linux') { + options.push( + + {process.platform === 'darwin' ? `Show ${this.state.appName} icon in the menu bar` : 'Show icon in the notification area'} + + {'Setting takes effect after restarting the app.'} + + ); + } + + if (process.platform === 'linux') { + options.push( + + {'Icon theme: '} + this.handleChangeTrayIconTheme('light', event)} + > + {'Light'} + + {' '} + this.handleChangeTrayIconTheme('dark', event)} + >{'Dark'} + , + ); + } + + if (process.platform === 'linux') { + options.push( + + {'Leave app running in notification area when application window is closed'} + + {'If enabled, the app stays running in the notification area after app window is closed.'} + {this.state.trayWasVisible || !this.state.showTrayIcon ? '' : ' Setting takes effect after restarting the app.'} + + ); + } + + options.push( + + {'Use GPU hardware acceleration'} + + {'If enabled, Mattermost UI is rendered more efficiently but can lead to decreased stability for some systems.'} + {' Setting takes effect after restarting the app.'} + + , + ); + + options.push( +
+
+
{'Download Location'}
+ + + + {'Specify the folder where files will download.'} + +
, + ); + + let optionsRow = null; + if (options.length > 0) { + optionsRow = ( + + +

{'App Options'}

+
+ +
+ { options.map((opt) => ( + + {opt} + + )) } + +
+ ); + } + + let waitForIpc; + if (this.state.ready) { + waitForIpc = ( + <> + {srvMgmt} + {optionsRow} + + ); + } else { + waitForIpc = (

{'Loading configuration...'}

); + } + + return ( +
+
+ +
+

{'Settings'}

+
+
+ + {waitForIpc} + +
+
+ ); + } +} + +SettingsPage.propTypes = { + openMenu: PropTypes.func.isRequired, +}; diff --git a/src/renderer/components/TabBar.jsx b/src/renderer/components/TabBar.jsx new file mode 100644 index 00000000..05c13a59 --- /dev/null +++ b/src/renderer/components/TabBar.jsx @@ -0,0 +1,154 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import {ipcRenderer} from 'electron'; +import PropTypes from 'prop-types'; +import {Nav, NavItem} from 'react-bootstrap'; +import {Container, Draggable} from 'react-smooth-dnd'; +import PlusIcon from 'mdi-react/PlusIcon'; + +import {GET_CONFIGURATION} from 'common/communication'; + +export default class TabBar extends React.PureComponent { // need "this" + constructor(props) { + super(props); + this.state = { + hasGPOTeams: false, + }; + } + + componentDidMount() { + ipcRenderer.invoke(GET_CONFIGURATION).then((config) => { + this.setState({hasGPOTeams: config.registryTeams && config.registryTeams.length > 0}); + }); + } + + render() { + const orderedTabs = this.props.teams.concat().sort((a, b) => a.order - b.order); + const tabs = orderedTabs.map((team) => { + const index = this.props.teams.indexOf(team); + + const sessionExpired = this.props.sessionsExpired[index]; + const hasUnreads = this.props.unreadCounts[index]; + + let mentionCount = 0; + if (this.props.mentionCounts[index] > 0) { + mentionCount = this.props.mentionCounts[index]; + } + + let badgeDiv; + if (sessionExpired) { + badgeDiv = ( +
+ ); + } else if (mentionCount !== 0) { + badgeDiv = ( +
+ {mentionCount} +
+ ); + } else if (hasUnreads) { + badgeDiv = ( +
+ ); + } + + const id = `teamTabItem${index}`; + const navItem = () => ( + { + this.props.onSelect(team.name, index); + }} + onSelect={() => { + this.props.onSelect(team.name, index); + }} + title={team.name} + disabled={this.props.tabsDisabled} + > +
+ + {team.name} + + { badgeDiv } +
+
+ ); + + return ( + ); + }); + if (this.props.showAddServerButton === true) { + tabs.push( + { + this.props.onAddServer(); + }} + disabled={this.props.tabsDisabled} + > +
+ +
+
, + ); + } + + const navContainer = (ref) => ( + + ); + return ( + { + return !this.state.hasGPOTeams && !this.props.tabsDisabled; + }} + /> + ); + } +} + +TabBar.propTypes = { + activeKey: PropTypes.number, + id: PropTypes.string, + isDarkMode: PropTypes.bool, + onSelect: PropTypes.func, + teams: PropTypes.array, + sessionsExpired: PropTypes.object, + unreadCounts: PropTypes.object, + mentionCounts: PropTypes.object, + showAddServerButton: PropTypes.bool, + onAddServer: PropTypes.func, + onDrop: PropTypes.func, + tabsDisabled: PropTypes.bool, +}; diff --git a/src/renderer/components/TeamList.jsx b/src/renderer/components/TeamList.jsx new file mode 100644 index 00000000..da96dbc2 --- /dev/null +++ b/src/renderer/components/TeamList.jsx @@ -0,0 +1,187 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; +import {ListGroup} from 'react-bootstrap'; + +import TeamListItem from './TeamListItem.jsx'; +import NewTeamModal from './NewTeamModal.jsx'; +import RemoveServerModal from './RemoveServerModal.jsx'; + +export default class TeamList extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + showEditTeamForm: false, + indexToRemoveServer: -1, + team: { + url: '', + name: '', + index: false, + order: props.teams.length, + }, + }; + } + + handleTeamRemove = (index) => { + console.log(index); + const teams = this.props.teams; + const removedOrder = this.props.teams[index].order; + teams.splice(index, 1); + teams.forEach((value) => { + if (value.order > removedOrder) { + value.order--; + } + }); + this.props.onTeamsChange(teams); + } + + handleTeamAdd = (team) => { + const teams = this.props.teams; + + // check if team already exists and then change existing team or add new one + if ((typeof team.index !== 'undefined') && teams[team.index]) { + teams[team.index].name = team.name; + teams[team.index].url = team.url; + teams[team.index].order = team.order; + } else { + teams.push(team); + } + + this.setState({ + showEditTeamForm: false, + team: { + url: '', + name: '', + index: false, + order: teams.length, + }, + }); + + this.props.onTeamsChange(teams); + } + + openServerRemoveModal = (indexForServer) => { + this.setState({indexToRemoveServer: indexForServer}); + } + + closeServerRemoveModal = () => { + this.setState({indexToRemoveServer: -1}); + } + + handleTeamRemovePrompt = (index) => { + return () => { + document.activeElement.blur(); + this.openServerRemoveModal(index); + }; + } + + handleTeamEditing = (team, index) => { + return () => { + document.activeElement.blur(); + this.setState({ + showEditTeamForm: true, + team: { + url: team.url, + name: team.name, + index, + order: team.order, + }, + }); + }; + } + + render() { + const teamNodes = this.props.teams.map((team, i) => { + return ( + this.props.onTeamClick(team.name)} + /> + ); + }); + + const addServerForm = ( + { + this.setState({ + showEditTeamForm: false, + team: { + name: '', + url: '', + index: false, + order: this.props.teams.length, + }, + }); + this.props.setAddTeamFormVisibility(false); + }} + onSave={(newTeam) => { + const teamData = { + name: newTeam.name, + url: newTeam.url, + order: newTeam.order, + }; + if (this.props.showAddTeamForm) { + this.props.addServer(teamData); + } else { + this.props.updateTeam(newTeam.index, teamData); + } + this.setState({ + showNewTeamModal: false, + showEditTeamForm: false, + team: { + name: '', + url: '', + index: false, + order: newTeam.order + 1, + }, + }); + this.render(); + this.props.setAddTeamFormVisibility(false); + }} + team={this.state.team} + />); + + const removeServer = this.props.teams[this.state.indexToRemoveServer]; + const removeServerModal = ( + { + this.handleTeamRemove(this.state.indexToRemoveServer); + this.closeServerRemoveModal(); + }} + /> + ); + + return ( + + { teamNodes } + { addServerForm } + { removeServerModal} + + ); + } +} + +TeamList.propTypes = { + onTeamClick: PropTypes.func, + onTeamsChange: PropTypes.func, + showAddTeamForm: PropTypes.bool, + teams: PropTypes.array, + addServer: PropTypes.func, + updateTeam: PropTypes.func, + setAddTeamFormVisibility: PropTypes.func, +}; diff --git a/src/renderer/components/TeamListItem.jsx b/src/renderer/components/TeamListItem.jsx new file mode 100644 index 00000000..d96a36da --- /dev/null +++ b/src/renderer/components/TeamListItem.jsx @@ -0,0 +1,49 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class TeamListItem extends React.PureComponent { + handleTeamRemove = () => { + this.props.onTeamRemove(); + } + handleTeamEditing = () => { + this.props.onTeamEditing(); + } + render() { + return ( +
+
+

{ this.props.name }

+

+ { this.props.url } +

+
+
+ {'Edit'} + {' - '} + {'Remove'} +
+
+ ); + } +} + +TeamListItem.propTypes = { + name: PropTypes.string, + onTeamEditing: PropTypes.func, + onTeamRemove: PropTypes.func, + onTeamClick: PropTypes.func, + url: PropTypes.string, +}; diff --git a/src/renderer/components/UpdaterPage.jsx b/src/renderer/components/UpdaterPage.jsx new file mode 100644 index 00000000..f833c396 --- /dev/null +++ b/src/renderer/components/UpdaterPage.jsx @@ -0,0 +1,113 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import propTypes from 'prop-types'; +import {Button, Navbar, ProgressBar} from 'react-bootstrap'; + +function InstallButton(props) { + if (props.notifyOnly) { + return ( + + ); + } + return ( + + ); +} + +InstallButton.propTypes = { + notifyOnly: propTypes.bool.isRequired, + onClickInstall: propTypes.func.isRequired, + onClickDownload: propTypes.func.isRequired, +}; + +function UpdaterPage(props) { + let footer; + if (props.isDownloading) { + footer = ( + + +
+ +
+
+ ); + } else { + footer = ( + + +
+ + +
+
+ ); + } + + return ( +
+ +

{'New update is available'}

+
+
+

{`A new version of the ${props.appName} is available!`}

+

{'Read the '} + {'release notes'} + {' to learn more.'} +

+
+ {footer} +
+ ); +} + +UpdaterPage.propTypes = { + appName: propTypes.string.isRequired, + notifyOnly: propTypes.bool.isRequired, + isDownloading: propTypes.bool.isRequired, + progress: propTypes.number, + onClickInstall: propTypes.func.isRequired, + onClickDownload: propTypes.func.isRequired, + onClickReleaseNotes: propTypes.func.isRequired, + onClickRemind: propTypes.func.isRequired, + onClickSkip: propTypes.func.isRequired, + onClickCancel: propTypes.func.isRequired, +}; + +export default UpdaterPage; diff --git a/src/renderer/components/UpdaterPage/UpdaterPage.stories.jsx b/src/renderer/components/UpdaterPage/UpdaterPage.stories.jsx new file mode 100644 index 00000000..0d6046c7 --- /dev/null +++ b/src/renderer/components/UpdaterPage/UpdaterPage.stories.jsx @@ -0,0 +1,53 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import {storiesOf} from '@storybook/react'; + +import {action} from '@storybook/addon-actions'; + +import UpdaterPage from '../UpdaterPage.jsx'; +import '../../css/components/UpdaterPage.css'; + +/* +appName: propTypes.string.isRequired, +notifyOnly: propTypes.bool.isRequired, +isDownloading: propTypes.bool.isRequired, +progress: propTypes.number, +onClickInstall: propTypes.func.isRequired, +onClickDownload: propTypes.func.isRequired, +onClickReleaseNotes: propTypes.func.isRequired, +onClickRemind: propTypes.func.isRequired, +onClickSkip: propTypes.func.isRequired, +*/ +const appName = 'Storybook App'; + +storiesOf('UpdaterPage', module). + add('Normal', () => ( + + )). + add('NotifyOnly', () => ( + + )). + add('Downloading', () => ( + + )); diff --git a/src/renderer/components/showCertificateModal.jsx b/src/renderer/components/showCertificateModal.jsx new file mode 100644 index 00000000..e6a24dcb --- /dev/null +++ b/src/renderer/components/showCertificateModal.jsx @@ -0,0 +1,114 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import {Modal, Button, Row, Col} from 'react-bootstrap'; + +export default class ShowCertificateModal extends React.PureComponent { + static propTypes = { + certificate: PropTypes.object, + onOk: PropTypes.func.isRequired, + }; + + constructor(props) { + super(props); + this.state = { + certificate: props.certificate, + }; + } + + handleOk = () => { + this.setState({certificate: null}); + this.props.onOk(); + } + + render() { + const certificateSection = (descriptor) => { + return ( + +
{descriptor}
+
+
+ ); + }; + const certificateItem = (descriptor, value) => { + const val = value ? `${value}` : ; + return ( + +
{descriptor}
+
{val}
+
+ ); + }; + + if (this.state.certificate === null) { + return ( + + + {'No certificate Selected'} + + + ); + } + + const utcSeconds = (date) => { + const d = new Date(0); + d.setUTCSeconds(date); + return d; + }; + + const expiration = utcSeconds(this.state.certificate.validExpiry); + const creation = utcSeconds(this.state.certificate.validStart); + const dateDisplayOptions = {dateStyle: 'full', timeStyle: 'full'}; + const dateLocale = 'en-US'; + return ( + + + {'Certificate information'} + + +

{'Details'}

+
+ {certificateSection('Subject Name')} + {certificateItem('Common Name', this.state.certificate.subject.commonName)} +
+
+ {certificateSection('Issuer Name')} + {certificateItem('Common Name', this.state.certificate.issuer.commonName)} +
+
+ {certificateItem('Serial Number', this.state.certificate.serialNumber)} + {certificateItem('Not Valid Before', creation.toLocaleString(dateLocale, dateDisplayOptions))} + {certificateItem('Not Valid After', expiration.toLocaleString(dateLocale, dateDisplayOptions))} +
+
+ {certificateSection('Public Key Info')} + {certificateItem('Algorithm', this.state.certificate.fingerprint.split('/')[0])} +
+
+ +
+ + + + + +
+
+
+ ); + } +} diff --git a/src/renderer/components/urlDescription.jsx b/src/renderer/components/urlDescription.jsx new file mode 100644 index 00000000..367cd2e7 --- /dev/null +++ b/src/renderer/components/urlDescription.jsx @@ -0,0 +1,19 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import propTypes from 'prop-types'; + +export default function UrlDescription(props) { + if (props.url) { + return ( +
+

{props.url}

+
+ ); + } +} + +UrlDescription.propTypes = { + url: propTypes.string, +}; diff --git a/src/renderer/css/components/AddServerModal.css b/src/renderer/css/components/AddServerModal.css new file mode 100644 index 00000000..42ec2a13 --- /dev/null +++ b/src/renderer/css/components/AddServerModal.css @@ -0,0 +1,179 @@ +html { + height: 100%; +} +body { + min-height: 100%; +} + +.NewTeamModal-noBottomSpace { + padding-bottom: 0px; + margin-bottom: 0px; +} + +.modal { + background-color: white; + color: #333; + border-radius: 4px; + border: 1px solid; + border-color: #666; + padding: 5px; + display: block; + font-size: 14px; + line-height: 1.4; + width: 1000px; + position: absolute; + top: 25%; + left: 50%; + transform: translate(-50%, -25%); + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; +} + +form.modalForm { + padding-left: 10px; + display: block; + color: #333; + font-size: 14px; + line-height: 1.4; +} + +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: 700; +} + +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + margin-right: 10px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + -webkit-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + box-sizing: border-box; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; +} + +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} + +button.modalButton { + color: #007bff; + border-color: #007bff; + display: inline-block; + font-weight: 400; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: .375rem .75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: .25rem; + transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; + box-sizing: border-box; + margin-left: 0.2em; + margin-right: 0.2em; +} + +button.modalButton:last-child { + margin-right: 0px; +} + +button.modalButton:focus { + text-decoration: none; +} + +button.modalButton:active:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +button.primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} + +button.primary:focus { + background-color: #286090; + border-color: #122b40; +} + +button.default { + color: #333; + background-color: #fff; + border-color: #ccc; +} + +button.default:focus { + background-color: #e6e6e6; + border-color: #8c8c8c; +} + +.modalHeader { + font-size: 18px; + font-weight: 500; + margin: 5px; + padding-bottom: 5px; + border-bottom: 0px; +} +.modalHeader::after { + border-bottom: 1px solid #e5e5e5; + content: ''; + display: block; + margin-top: 10px; +} + +.modalFooter { + margin: 5px; + padding-top: 10px; + padding-left: 10px; + padding-right: 10px; + text-align: right; + border-top: 0px; +} + +.modalFooter::before { + border-top: 1px solid #e5e5e5; + content: ''; + display: block; + margin-bottom: 10px; +} + +.pull-left { + float: left; +} +.modal-error { + color: #a94442; +} + +.has-error .form-control { + border-color: #a94442; +} + diff --git a/src/browser/css/components/CertificateModal.css b/src/renderer/css/components/CertificateModal.css similarity index 100% rename from src/browser/css/components/CertificateModal.css rename to src/renderer/css/components/CertificateModal.css diff --git a/src/browser/css/components/ErrorView.css b/src/renderer/css/components/ErrorView.css similarity index 100% rename from src/browser/css/components/ErrorView.css rename to src/renderer/css/components/ErrorView.css diff --git a/src/browser/css/components/ExtraBar.css b/src/renderer/css/components/ExtraBar.css similarity index 100% rename from src/browser/css/components/ExtraBar.css rename to src/renderer/css/components/ExtraBar.css diff --git a/src/browser/css/components/Finder.css b/src/renderer/css/components/Finder.css similarity index 96% rename from src/browser/css/components/Finder.css rename to src/renderer/css/components/Finder.css index 4d9ac338..4d15e072 100644 --- a/src/browser/css/components/Finder.css +++ b/src/renderer/css/components/Finder.css @@ -1,7 +1,6 @@ .finder { position: fixed; top: 0px; - right: 200px; padding: 4px; background: #eee; border: 1px solid #d7d7d7; @@ -13,10 +12,6 @@ -webkit-app-region: no-drag; } -.finder.macOS { - right: 20px; -} - .finder-input-wrapper { display: inline-block; position: relative; @@ -81,4 +76,4 @@ border-top-left-radius: 3px; border-bottom-left-radius: 3px; margin-left: 2px; -} \ No newline at end of file +} diff --git a/src/browser/css/components/HoveringURL.css b/src/renderer/css/components/HoveringURL.css similarity index 66% rename from src/browser/css/components/HoveringURL.css rename to src/renderer/css/components/HoveringURL.css index 947e0af3..94a4fddf 100644 --- a/src/browser/css/components/HoveringURL.css +++ b/src/renderer/css/components/HoveringURL.css @@ -1,18 +1,30 @@ .HoveringURL { + position: fixed; + bottom: 0; + left: 0; color: gray; background-color: whitesmoke; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - padding-left: 4px; + padding-left: 0px; padding-right: 16px; padding-top: 2px; padding-bottom: 2px; border-top: solid thin lightgray; pointer-events: none; + margin: 0; } .HoveringURL-left { border-top-right-radius: 4px; border-right: solid thin lightgray; } + +.HoveringURL p { + margin: 0; + font-size: small; + font-family: "Open Sans", sans-serif; + padding-left: 4px; + padding-right: 4px; +} diff --git a/src/browser/css/components/LoadingAnimation.css b/src/renderer/css/components/LoadingAnimation.css similarity index 100% rename from src/browser/css/components/LoadingAnimation.css rename to src/renderer/css/components/LoadingAnimation.css diff --git a/src/browser/css/components/LoadingScreen.css b/src/renderer/css/components/LoadingScreen.css similarity index 95% rename from src/browser/css/components/LoadingScreen.css rename to src/renderer/css/components/LoadingScreen.css index ccffd659..2694b5ab 100644 --- a/src/browser/css/components/LoadingScreen.css +++ b/src/renderer/css/components/LoadingScreen.css @@ -1,9 +1,13 @@ +body { + background-color: transparent; +} + .LoadingScreen { display: flex; justify-content: center; align-items: center; position: absolute; - top: 40px; + top: 0px; right: 0px; bottom: 0px; left: 0px; diff --git a/src/browser/css/components/MainPage.css b/src/renderer/css/components/MainPage.css similarity index 100% rename from src/browser/css/components/MainPage.css rename to src/renderer/css/components/MainPage.css diff --git a/src/browser/css/components/MattermostView.css b/src/renderer/css/components/MattermostView.css similarity index 100% rename from src/browser/css/components/MattermostView.css rename to src/renderer/css/components/MattermostView.css diff --git a/src/browser/css/components/NewTeamModal.css b/src/renderer/css/components/NewTeamModal.css similarity index 100% rename from src/browser/css/components/NewTeamModal.css rename to src/renderer/css/components/NewTeamModal.css diff --git a/src/browser/css/components/PermissionRequestDialog.css b/src/renderer/css/components/PermissionRequestDialog.css similarity index 100% rename from src/browser/css/components/PermissionRequestDialog.css rename to src/renderer/css/components/PermissionRequestDialog.css diff --git a/src/browser/css/components/TabBar.css b/src/renderer/css/components/TabBar.css similarity index 92% rename from src/browser/css/components/TabBar.css rename to src/renderer/css/components/TabBar.css index c59ada97..5d733ed2 100644 --- a/src/browser/css/components/TabBar.css +++ b/src/renderer/css/components/TabBar.css @@ -167,22 +167,22 @@ margin-left: -1px; } -.TabBar>li.teamTabItem:not(.active):hover+.TabBar-addServerButton>a>div.TabBar-tabSeperator { +.TabBar>li.teamTabItem:not(.active):not(.disabled):hover+.TabBar-addServerButton>a>div.TabBar-tabSeperator { border-left: none; margin-left: 0px; } -.TabBar>li.teamTabItem:not(.active):hover+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator { +.TabBar>li.teamTabItem:not(.active):not(.disabled):hover+li.teamTabItem:not(.active)>a>div.TabBar-tabSeperator { border-left: none; margin-left: 0px; } -.TabBar>li.teamTabItem:not(.active)+.TabBar-addServerButton>a:hover>div.TabBar-tabSeperator { +.TabBar>li.teamTabItem:not(.active):not(.disabled)+.TabBar-addServerButton>a:hover>div.TabBar-tabSeperator { border-left: none; margin-left: 0px; } -.TabBar>li.teamTabItem:not(.active)+li.teamTabItem:not(.active)>a:hover>div.TabBar-tabSeperator { +.TabBar>li.teamTabItem:not(.active):not(.disabled)+li.teamTabItem:not(.active)>a:hover>div.TabBar-tabSeperator { border-left: none; margin-left: 0px; } diff --git a/src/browser/css/components/TeamListItem.css b/src/renderer/css/components/TeamListItem.css similarity index 100% rename from src/browser/css/components/TeamListItem.css rename to src/renderer/css/components/TeamListItem.css diff --git a/src/browser/css/components/UpdaterPage.css b/src/renderer/css/components/UpdaterPage.css similarity index 100% rename from src/browser/css/components/UpdaterPage.css rename to src/renderer/css/components/UpdaterPage.css diff --git a/src/browser/css/components/index.css b/src/renderer/css/components/index.css similarity index 100% rename from src/browser/css/components/index.css rename to src/renderer/css/components/index.css diff --git a/src/browser/css/index.css b/src/renderer/css/index.css similarity index 100% rename from src/browser/css/index.css rename to src/renderer/css/index.css diff --git a/src/renderer/css/modals.css b/src/renderer/css/modals.css new file mode 100644 index 00000000..67843393 --- /dev/null +++ b/src/renderer/css/modals.css @@ -0,0 +1,3 @@ +body { + background-color: transparent; +} diff --git a/src/browser/css/settings.css b/src/renderer/css/settings.css similarity index 90% rename from src/browser/css/settings.css rename to src/renderer/css/settings.css index 8d380bfb..f5591a88 100644 --- a/src/browser/css/settings.css +++ b/src/renderer/css/settings.css @@ -26,10 +26,11 @@ } body { - overflow: hidden; + overflow-x: hidden; + overflow-y: scroll; height: 100%; } #content { height: 100%; -} \ No newline at end of file +} diff --git a/src/browser/hooks/useAnimationEnd.js b/src/renderer/hooks/useAnimationEnd.js similarity index 53% rename from src/browser/hooks/useAnimationEnd.js rename to src/renderer/hooks/useAnimationEnd.js index be7a0310..2fff2ce7 100644 --- a/src/browser/hooks/useAnimationEnd.js +++ b/src/renderer/hooks/useAnimationEnd.js @@ -13,32 +13,32 @@ import React from 'react'; * ignores events bubbling up from descendent elements */ function useAnimationEnd( - ref, - callback, - animationName, - listenForEventBubbling = true, + ref, + callback, + animationName, + listenForEventBubbling = true, ) { - React.useEffect(() => { - if (!ref.current) { - return; - } + React.useEffect(() => { + if (!ref.current) { + return undefined; + } - function handleAnimationend(event) { - if (!listenForEventBubbling && event.target !== ref.current) { - return; - } - if (animationName && animationName !== event.animationName) { - return; - } - callback(event); - } + function handleAnimationend(event) { + if (!listenForEventBubbling && event.target !== ref.current) { + return; + } + if (animationName && animationName !== event.animationName) { + return; + } + callback(event); + } - ref.current.addEventListener('animationend', handleAnimationend); + ref.current.addEventListener('animationend', handleAnimationend); - return () => { - ref.current.removeEventListener('animationend', handleAnimationend); - }; - }, [ref, callback, animationName, listenForEventBubbling]); + return () => { + ref.current.removeEventListener('animationend', handleAnimationend); + }; + }, [ref, callback, animationName, listenForEventBubbling]); } export default useAnimationEnd; diff --git a/src/renderer/hooks/useTransitionEnd.js b/src/renderer/hooks/useTransitionEnd.js new file mode 100644 index 00000000..fecb0ea3 --- /dev/null +++ b/src/renderer/hooks/useTransitionEnd.js @@ -0,0 +1,57 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +/** + * A custom hook to implement a transitionend listener on the provided ref + * @param {object} ref - A reference to a DOM element to add the listener to + * @param {function} callback - A callback function that will be run for matching animation events + * @param {array} properties - An array of css property strings to listen for + * @param {boolean} listenForEventBubbling - A parameter that when true, listens for events on the target element and + * bubbled from all descendent elements but when false, only listens for events coming from the target element and + * ignores events bubbling up from descendent elements + */ +function useTransitionend( + ref, + callback, + properties, + listenForEventBubbling = true, +) { + React.useEffect(() => { + if (!ref.current) { + return undefined; + } + + function handleTransitionEnd(event) { + if (!listenForEventBubbling && event.target !== ref.current) { + return; + } + + if (properties && typeof properties === 'object') { + const property = properties.find( + (propertyName) => propertyName === event.propertyName, + ); + if (property) { + callback(event); + } + return; + } + callback(event); + } + + ref.current.addEventListener('transitionend', handleTransitionEnd); + + return () => { + if (!ref.current) { + return; + } + ref.current.removeEventListener( + 'transitionend', + handleTransitionEnd, + ); + }; + }, [ref, callback, properties, listenForEventBubbling]); +} + +export default useTransitionend; diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 00000000..0c400db8 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,10 @@ + + + + + <%= htmlWebpackPlugin.options.title %> + + +
+ + \ No newline at end of file diff --git a/src/renderer/index.jsx b/src/renderer/index.jsx new file mode 100644 index 00000000..4e17551b --- /dev/null +++ b/src/renderer/index.jsx @@ -0,0 +1,133 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/index.css'; + +if (process.env.NODE_ENV === 'production') { + window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval + throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.'); + }; +} else if (module.hot) { + module.hot.accept(); +} + +import React from 'react'; +import ReactDOM from 'react-dom'; +import {ipcRenderer} from 'electron'; + +import {GET_CONFIGURATION, UPDATE_TEAMS, QUIT, RELOAD_CONFIGURATION} from 'common/communication'; + +import MainPage from './components/MainPage.jsx'; +class Root extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + + async componentDidMount() { + await this.setInitialConfig(); + + ipcRenderer.on('synchronize-config', () => { + this.reloadConfig(); + }); + + ipcRenderer.on(RELOAD_CONFIGURATION, () => { + this.reloadConfig(); + }); + + // Deny drag&drop navigation in mainWindow. + // Drag&drop is allowed in webview of index.html. + document.addEventListener('dragover', (event) => event.preventDefault()); + document.addEventListener('drop', (event) => event.preventDefault()); + } + + setInitialConfig = async () => { + const config = await this.requestConfig(true); + this.setState({config}); + } + + moveTabs = async (originalOrder, newOrder) => { + const teams = this.state.config.teams.concat(); + const tabOrder = teams.map((team, index) => { + return { + index, + order: team.order, + }; + }).sort((a, b) => (a.order - b.order)); + + const team = tabOrder.splice(originalOrder, 1); + tabOrder.splice(newOrder, 0, team[0]); + + let teamIndex; + tabOrder.forEach((t, order) => { + if (order === newOrder) { + teamIndex = t.index; + } + teams[t.index].order = order; + }); + await this.teamConfigChange(teams); + return teamIndex; + }; + + teamConfigChange = async (updatedTeams, callback) => { + const updatedConfig = await ipcRenderer.invoke(UPDATE_TEAMS, updatedTeams); + await this.reloadConfig(); + if (callback) { + callback(updatedConfig); + } + }; + + reloadConfig = async () => { + const config = await this.requestConfig(); + this.setState({config}); + }; + + requestConfig = async (exitOnError) => { + // todo: should we block? + try { + const configRequest = await ipcRenderer.invoke(GET_CONFIGURATION); + return configRequest; + } catch (err) { + console.log(`there was an error with the config: ${err}`); + if (exitOnError) { + ipcRenderer.send(QUIT, `unable to load configuration: ${err}`, err.stack); + } + } + return null; + }; + + openMenu = () => { + if (process.platform !== 'darwin') { + ipcRenderer.send('open-app-menu'); + } + } + + render() { + const {config} = this.state; + if (!config) { + return null; + } + + return ( + + ); + } +} +ipcRenderer.invoke('get-app-version').then(({name, version}) => { + // eslint-disable-next-line no-undef + console.log(`Starting ${name} v${version} commit: ${__HASH_VERSION__}`); +}); + +ReactDOM.render( + , + document.getElementById('app'), +); diff --git a/src/renderer/modals/certificate/certificate.jsx b/src/renderer/modals/certificate/certificate.jsx new file mode 100644 index 00000000..6dc6a858 --- /dev/null +++ b/src/renderer/modals/certificate/certificate.jsx @@ -0,0 +1,38 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import {MODAL_CANCEL, MODAL_RESULT, RETRIEVE_MODAL_INFO} from 'common/communication.js'; + +import SelectCertificateModal from './certificateModal.jsx'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/modals.css'; +import 'renderer/css/components/CertificateModal.css'; + +const handleCancel = () => { + window.postMessage({type: MODAL_CANCEL}, window.location.href); +}; + +const handleSelect = (cert) => { + window.postMessage({type: MODAL_RESULT, data: {cert}}, window.location.href); +}; + +const getCertInfo = () => { + window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href); +}; + +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +start(); diff --git a/src/renderer/modals/certificate/certificateModal.jsx b/src/renderer/modals/certificate/certificateModal.jsx new file mode 100644 index 00000000..74078656 --- /dev/null +++ b/src/renderer/modals/certificate/certificateModal.jsx @@ -0,0 +1,182 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import {Modal, Button, Table, Row, Col} from 'react-bootstrap'; + +import {MODAL_INFO} from 'common/communication'; + +import ShowCertificateModal from '../../components/showCertificateModal.jsx'; + +export default class SelectCertificateModal extends React.PureComponent { + static propTypes = { + onSelect: PropTypes.func.isRequired, + onCancel: PropTypes.func, + getCertInfo: PropTypes.func, + } + + constructor(props) { + super(props); + this.state = { + selectedIndex: null, + showCertificate: null, + }; + } + + componentDidMount() { + window.addEventListener('message', this.handleCertInfoMessage); + + this.props.getCertInfo(); + } + + componentWillUnmount() { + window.removeEventListener('message', this.handleCertInfoMessage); + } + + handleCertInfoMessage = (event) => { + switch (event.data.type) { + case MODAL_INFO: { + const {url, list} = event.data.data; + this.setState({url, list}); + break; + } + default: + break; + } + } + + selectfn = (index) => { + return (() => { + this.setState({selectedIndex: index}); + }); + }; + + renderCert = (cert, index) => { + const issuer = (cert.issuerName || (cert.issuer && cert.issuer.commonName) || ''); + const subject = (cert.subjectName || (cert.subject && cert.subject.commonName) || ''); + const serial = cert.serialNumber || ''; + + return ( + + {subject} + {issuer} + {serial} + ); + }; + + renderCerts = (certificateList) => { + if (certificateList) { + const certs = certificateList.map(this.renderCert); + return ( + + {certs} + + ); + } + return ({'No certificates available'}); + } + + getSelectedCert = () => { + return this.state.selectedIndex === null ? null : this.state.list[this.state.selectedIndex]; + }; + + handleOk = () => { + const cert = this.getSelectedCert(); + if (cert !== null) { + this.props.onSelect(cert); + } + } + + handleCertificateInfo = () => { + const certificate = this.getSelectedCert(); + this.setState({showCertificate: certificate}); + } + + certificateInfoClose = () => { + this.setState({showCertificate: null}); + } + + render() { + if (this.state.showCertificate) { + return ( + + ); + } + + return ( + + + {'Select a certificate'} + + +

{`Select a certificate to authenticate yourself to ${this.state.url}`}

+ + + + + + + + + + {this.renderCerts(this.state.list)} + + +
{'Subject'}{'Issuer'}{'Serial'}
+
+ +
+ + + + + + + + + +
+
+
+ ); + } +} diff --git a/src/renderer/modals/finder/finder.jsx b/src/renderer/modals/finder/finder.jsx new file mode 100644 index 00000000..76cb0a9a --- /dev/null +++ b/src/renderer/modals/finder/finder.jsx @@ -0,0 +1,178 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class Finder extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + foundInPage: false, + searchTxt: '', + }; + } + + componentDidMount() { + this.searchInput.focus(); + + // synthetic events are not working all that reliably for touch bar with esc keys + this.searchInput.addEventListener('keyup', this.handleKeyEvent); + } + + static getDerivedStateFromProps(props, state) { + if (state.searchTxt) { + return { + foundInPage: Boolean(props.matches), + matches: `${props.activeMatchOrdinal}/${props.matches}`, + }; + } + + return {matches: '0/0'}; + } + + findNext = () => { + this.props.findInPage(this.state.searchTxt, { + forward: true, + findNext: true, + }); + }; + + find = (keyword) => { + this.props.stopFindInPage('clearSelection'); + if (keyword) { + this.props.findInPage(keyword); + } else { + this.setState({ + matches: '0/0', + }); + } + }; + + findPrev = () => { + this.props.findInPage(this.state.searchTxt, {forward: false, findNext: true}); + } + + searchTxt = (event) => { + this.setState({searchTxt: event.target.value}); + this.find(event.target.value); + } + + handleKeyEvent = (event) => { + if (event.code === 'Escape') { + this.close(); + } else if (event.code === 'Enter') { + this.findNext(); + } + } + + close = () => { + this.searchInput.removeEventListener('keyup', this.handleKeyEvent); + this.props.stopFindInPage('clearSelection'); + this.props.close(); + } + + render() { + return ( +
+
+
+ { + this.searchInput = input; + }} + /> + {this.state.matches} +
+ + + +
+
+ ); + } +} + +Finder.propTypes = { + close: PropTypes.func, + findInPage: PropTypes.func, + stopFindInPage: PropTypes.func, + activeMatchOrdinal: PropTypes.number, + matches: PropTypes.number, + focus: PropTypes.func, +}; diff --git a/src/renderer/modals/finder/index.jsx b/src/renderer/modals/finder/index.jsx new file mode 100644 index 00000000..09bdfe79 --- /dev/null +++ b/src/renderer/modals/finder/index.jsx @@ -0,0 +1,73 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import {FIND_IN_PAGE, STOP_FIND_IN_PAGE, CLOSE_FINDER, FOUND_IN_PAGE, FOCUS_FINDER} from 'common/communication.js'; + +import Finder from './finder.jsx'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/modals.css'; +import 'renderer/css/components/Finder.css'; + +const closeFinder = () => { + window.postMessage({type: CLOSE_FINDER}, window.location.href); +}; + +const findInPage = (searchText, options) => { + window.postMessage({type: FIND_IN_PAGE, data: {searchText, options}}, window.location.href); +}; + +const stopFindInPage = (action) => { + window.postMessage({type: STOP_FIND_IN_PAGE, data: action}, window.location.href); +}; + +const focusFinder = () => { + window.postMessage({type: FOCUS_FINDER}, window.location.href); +}; +class FinderRoot extends React.PureComponent { + constructor() { + super(); + this.state = {}; + } + + componentDidMount() { + window.addEventListener('message', this.handleMessageEvent); + } + + componentWillUnmount() { + window.removeEventListener('message', this.handleMessageEvent); + } + + handleMessageEvent = (event) => { + if (event.data.type === FOUND_IN_PAGE) { + this.setState({ + activeMatchOrdinal: event.data.data.activeMatchOrdinal, + matches: event.data.data.matches, + }); + } + } + + render() { + return ( + + ); + } +} +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +start(); diff --git a/src/renderer/modals/loadingScreen/index.jsx b/src/renderer/modals/loadingScreen/index.jsx new file mode 100644 index 00000000..a9c3074e --- /dev/null +++ b/src/renderer/modals/loadingScreen/index.jsx @@ -0,0 +1,70 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import {RECEIVED_LOADING_SCREEN_DATA, GET_LOADING_SCREEN_DATA, LOADING_SCREEN_ANIMATION_FINISHED, TOGGLE_LOADING_SCREEN_VISIBILITY} from 'common/communication.js'; + +import LoadingScreen from '../../components/LoadingScreen.jsx'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/modals.css'; +import 'renderer/css/components/LoadingAnimation.css'; +import 'renderer/css/components/LoadingScreen.css'; + +class LoadingScreenRoot extends React.PureComponent { + constructor() { + super(); + this.state = { + showLoadingScreen: true, + darkMode: false, + }; + } + + componentDidMount() { + window.postMessage({type: GET_LOADING_SCREEN_DATA}, window.location.href); + + window.addEventListener('message', this.handleMessageEvent); + } + + componentWillUnmount() { + window.removeEventListener('message', this.handleMessageEvent); + } + + handleMessageEvent = (event) => { + if (event.data.type === RECEIVED_LOADING_SCREEN_DATA) { + this.setState({ + darkMode: event.data.data.darkMode, + }); + } + + if (event.data.type === TOGGLE_LOADING_SCREEN_VISIBILITY) { + this.setState({ + showLoadingScreen: event.data.data, + }); + } + } + + onFadeOutComplete = () => { + window.postMessage({type: LOADING_SCREEN_ANIMATION_FINISHED}, window.location.href); + } + + render() { + return ( + + ); + } +} +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +start(); diff --git a/src/renderer/modals/login/login.jsx b/src/renderer/modals/login/login.jsx new file mode 100644 index 00000000..86e4f8b2 --- /dev/null +++ b/src/renderer/modals/login/login.jsx @@ -0,0 +1,37 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import {MODAL_CANCEL, MODAL_RESULT, RETRIEVE_MODAL_INFO} from 'common/communication.js'; + +import LoginModal from './loginModal.jsx'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/modals.css'; + +const handleLoginCancel = (request) => { + window.postMessage({type: MODAL_CANCEL, data: {request}}, window.location.href); +}; + +const handleLogin = (request, username, password) => { + window.postMessage({type: MODAL_RESULT, data: {request, username, password}}, window.location.href); +}; + +const getAuthInfo = () => { + window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href); +}; + +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +start(); diff --git a/src/renderer/modals/login/loginModal.jsx b/src/renderer/modals/login/loginModal.jsx new file mode 100644 index 00000000..9fa1fa60 --- /dev/null +++ b/src/renderer/modals/login/loginModal.jsx @@ -0,0 +1,156 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Button, Col, ControlLabel, Form, FormGroup, FormControl, Modal} from 'react-bootstrap'; + +import {MODAL_INFO} from 'common/communication'; +import urlUtils from 'common/utils/url'; + +export default class LoginModal extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + username: '', + password: '', + request: null, + authInfo: null, + }; + } + + componentDidMount() { + window.addEventListener('message', this.handleAuthInfoMessage); + + this.props.getAuthInfo(); + } + + componentWillUnmount() { + window.removeEventListener('message', this.handleAuthInfoMessage); + } + + handleAuthInfoMessage = (event) => { + switch (event.data.type) { + case MODAL_INFO: { + const {request, authInfo} = event.data.data; + this.setState({request, authInfo}); + break; + } + default: + break; + } + } + + handleSubmit = (event) => { + event.preventDefault(); + this.props.onLogin(this.state.request, this.state.username, this.state.password); + this.setState({ + username: '', + password: '', + request: null, + authInfo: null, + }); + } + + handleCancel = (event) => { + event.preventDefault(); + this.props.onCancel(this.state.request); + this.setState({ + username: '', + password: '', + request: null, + authInfo: null, + }); + } + + setUsername = (e) => { + this.setState({username: e.target.value}); + } + + setPassword = (e) => { + this.setState({password: e.target.value}); + } + + render() { + let theServer = ''; + if (!(this.state.request && this.state.authInfo)) { + theServer = ''; + } else if (this.state.authInfo.isProxy) { + theServer = `The proxy ${this.state.authInfo.host}:${this.state.authInfo.port}`; + } else { + const tmpURL = urlUtils.parseURL(this.state.request.url); + theServer = `The server ${tmpURL.protocol}//${tmpURL.host}`; + } + const message = `${theServer} requires a username and password.`; + return ( + + + {'Authentication Required'} + + +

+ { message } +

+
+ + {'User Name'} + + { + e.stopPropagation(); + }} + /> + + + + {'Password'} + + { + e.stopPropagation(); + }} + /> + + + + +
+ + { ' ' } + +
+ +
+
+
+
+ ); + } +} + +LoginModal.propTypes = { + onCancel: PropTypes.func, + onLogin: PropTypes.func, + getAuthInfo: PropTypes.func, +}; diff --git a/src/renderer/modals/newServer/newServer.jsx b/src/renderer/modals/newServer/newServer.jsx new file mode 100644 index 00000000..88efc867 --- /dev/null +++ b/src/renderer/modals/newServer/newServer.jsx @@ -0,0 +1,38 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/modals.css'; + +const queryString = window.location.search; +const urlParams = new URLSearchParams(queryString); + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import {MODAL_CANCEL, MODAL_RESULT} from 'common/communication.js'; + +import NewTeamModal from '../../components/NewTeamModal.jsx'; //'./addServer.jsx'; + +const onClose = () => { + window.postMessage({type: MODAL_CANCEL}, window.location.href); +}; + +const onSave = (data) => { + window.postMessage({type: MODAL_RESULT, data}, window.location.href); +}; + +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +start(); diff --git a/src/renderer/modals/permission/permission.jsx b/src/renderer/modals/permission/permission.jsx new file mode 100644 index 00000000..7ca08a10 --- /dev/null +++ b/src/renderer/modals/permission/permission.jsx @@ -0,0 +1,42 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import {MODAL_CANCEL, MODAL_RESULT, RETRIEVE_MODAL_INFO, MODAL_SEND_IPC_MESSAGE} from 'common/communication.js'; + +import PermissionModal from './permissionModal.jsx'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/modals.css'; + +const handleDeny = () => { + window.postMessage({type: MODAL_CANCEL}, window.location.href); +}; + +const handleGrant = () => { + window.postMessage({type: MODAL_RESULT}, window.location.href); +}; + +const getPermissionInfo = () => { + window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href); +}; + +const openExternalLink = (protocol, url) => { + window.postMessage({type: MODAL_SEND_IPC_MESSAGE, data: {type: 'confirm-protocol', args: [protocol, url]}}, window.location.href); +}; + +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +start(); diff --git a/src/renderer/modals/permission/permissionModal.jsx b/src/renderer/modals/permission/permissionModal.jsx new file mode 100644 index 00000000..bcab763a --- /dev/null +++ b/src/renderer/modals/permission/permissionModal.jsx @@ -0,0 +1,109 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Modal, Button} from 'react-bootstrap'; +import PropTypes from 'prop-types'; + +import urlUtil from 'common/utils/url'; +import {MODAL_INFO} from 'common/communication'; +import {PERMISSION_DESCRIPTION} from 'common/permissions'; + +export default class PermissionModal extends React.PureComponent { + constructor(props) { + super(props); + this.state = {}; + } + + componentDidMount() { + window.addEventListener('message', this.handlePermissionInfoMessage); + + this.props.getPermissionInfo(); + } + + componentWillUnmount() { + window.removeEventListener('message', this.handlePermissionInfoMessage); + } + + handlePermissionInfoMessage = (event) => { + switch (event.data.type) { + case MODAL_INFO: { + const {url, permission} = event.data.data; + this.setState({url, permission}); + break; + } + default: + break; + } + } + + getModalTitle() { + return `${PERMISSION_DESCRIPTION[this.state.permission]} Required`; + } + + getModalBody() { + const {url, permission} = this.state; + const originDisplay = url ? urlUtil.getHost(url) : 'unknown origin'; + const originLink = url ? originDisplay : ''; + + const click = (e) => { + e.preventDefault(); + let parseUrl; + try { + parseUrl = urlUtil.parseURL(originLink); + this.props.openExternalLink(parseUrl.protocol, originLink); + } catch (err) { + console.error(`invalid url ${originLink} supplied to externallink: ${err}`); + } + }; + + return ( +
+

+ {`A site that's not included in your Mattermost server configuration requires access for ${PERMISSION_DESCRIPTION[permission]}.`} +

+

+ {'This request originated from '} + {originDisplay} +

+
+ ); + } + + render() { + return ( + + + {this.getModalTitle()} + + + {this.getModalBody()} + + +
+ + +
+
+
+ ); + } +} + +PermissionModal.propTypes = { + handleDeny: PropTypes.func, + handleGrant: PropTypes.func, + getPermissionInfo: PropTypes.func, + openExternalLink: PropTypes.func, +}; diff --git a/src/renderer/modals/urlView/index.html b/src/renderer/modals/urlView/index.html new file mode 100644 index 00000000..e615c73a --- /dev/null +++ b/src/renderer/modals/urlView/index.html @@ -0,0 +1,8 @@ + + + + + +
+ + \ No newline at end of file diff --git a/src/renderer/modals/urlView/urlView.jsx b/src/renderer/modals/urlView/urlView.jsx new file mode 100644 index 00000000..25cdab2d --- /dev/null +++ b/src/renderer/modals/urlView/urlView.jsx @@ -0,0 +1,23 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import 'renderer/css/components/HoveringURL.css'; + +const queryString = window.location.search; +const urlParams = new URLSearchParams(queryString); + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import UrlDescription from '../../components/urlDescription.jsx'; + +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +start(); diff --git a/src/renderer/notificationSounds.js b/src/renderer/notificationSounds.js new file mode 100644 index 00000000..bd471262 --- /dev/null +++ b/src/renderer/notificationSounds.js @@ -0,0 +1,30 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {throttle} from 'underscore'; + +import ding from 'static/sounds/ding.mp3'; +import bing from 'static/sounds/bing.mp3'; +import crackle from 'static/sounds/crackle.mp3'; +import down from 'static/sounds/down.mp3'; +import hello from 'static/sounds/hello.mp3'; +import ripple from 'static/sounds/ripple.mp3'; +import upstairs from 'static/sounds/upstairs.mp3'; + +export const DEFAULT_WIN7 = 'Ding'; +const notificationSounds = new Map([ + [DEFAULT_WIN7, ding], + ['Bing', bing], + ['Crackle', crackle], + ['Down', down], + ['Hello', hello], + ['Ripple', ripple], + ['Upstairs', upstairs], +]); + +export const playSound = throttle((soundName) => { + if (soundName) { + const audio = new Audio(notificationSounds.get(soundName)); + audio.play(); + } +}, 3000, {trailing: false}); diff --git a/src/renderer/settings.jsx b/src/renderer/settings.jsx new file mode 100644 index 00000000..deea2a97 --- /dev/null +++ b/src/renderer/settings.jsx @@ -0,0 +1,43 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import {ipcRenderer} from 'electron'; + +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'renderer/css/index.css'; +import 'renderer/css/settings.css'; + +if (process.env.NODE_ENV === 'production') { + window.eval = global.eval = () => { // eslint-disable-line no-multi-assign, no-eval + throw new Error('Sorry, Mattermost does not support window.eval() for security reasons.'); + }; +} else if (module.hot) { + module.hot.accept(); +} + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import SettingsPage from './components/SettingsPage.jsx'; + +function openMenu() { + if (process.platform !== 'darwin') { + ipcRenderer.send('open-app-menu'); + } +} + +const start = async () => { + ReactDOM.render( + , + document.getElementById('app'), + ); +}; + +// Deny drag&drop navigation in mainWindow. +document.addEventListener('dragover', (event) => event.preventDefault()); +document.addEventListener('drop', (event) => event.preventDefault()); + +start(); diff --git a/src/browser/updater.html b/src/renderer/updater.html similarity index 100% rename from src/browser/updater.html rename to src/renderer/updater.html diff --git a/src/renderer/updater.jsx b/src/renderer/updater.jsx new file mode 100644 index 00000000..7d2456db --- /dev/null +++ b/src/renderer/updater.jsx @@ -0,0 +1,157 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai + +import url from 'url'; + +import React from 'react'; +import ReactDOM from 'react-dom'; +import propTypes from 'prop-types'; +import {ipcRenderer, remote} from 'electron'; + +import UpdaterPage from './components/UpdaterPage.jsx'; + +const thisURL = url.parse(location.href, true); +const notifyOnly = thisURL.query.notifyOnly === 'true'; + +class UpdaterPageContainer extends React.PureComponent { + constructor(props) { + super(props); + this.state = props.initialState; + } + + getTabWebContents() { + return remote.webContents.getFocusedWebContents(); + } + + componentDidMount() { + ipcRenderer.on('start-download', () => { + this.setState({ + isDownloading: true, + }); + }); + ipcRenderer.on('progress', (event, progress) => { + this.setState({ + progress, + }); + }); + ipcRenderer.on('zoom-in', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + if (activeTabWebContents.zoomLevel >= 9) { + return; + } + activeTabWebContents.zoomLevel += 1; + }); + + ipcRenderer.on('zoom-out', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + if (activeTabWebContents.zoomLevel <= -8) { + return; + } + activeTabWebContents.zoomLevel -= 1; + }); + + ipcRenderer.on('zoom-reset', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + activeTabWebContents.zoomLevel = 0; + }); + + ipcRenderer.on('undo', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + activeTabWebContents.undo(); + }); + + ipcRenderer.on('redo', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + activeTabWebContents.redo(); + }); + + ipcRenderer.on('cut', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + activeTabWebContents.cut(); + }); + + ipcRenderer.on('copy', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + activeTabWebContents.copy(); + }); + + ipcRenderer.on('paste', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + activeTabWebContents.paste(); + }); + + ipcRenderer.on('paste-and-match', () => { + const activeTabWebContents = this.getTabWebContents(); + if (!activeTabWebContents) { + return; + } + activeTabWebContents.pasteAndMatchStyle(); + }); + } + + render() { + return ( + { + ipcRenderer.send('click-release-notes'); + }} + onClickSkip={() => { + ipcRenderer.send('click-skip'); + }} + onClickRemind={() => { + ipcRenderer.send('click-remind'); + }} + onClickInstall={() => { + ipcRenderer.send('click-install'); + }} + onClickDownload={() => { + ipcRenderer.send('click-download'); + }} + onClickCancel={() => { + ipcRenderer.send('click-cancel'); + }} + /> + ); + } +} + +UpdaterPageContainer.propTypes = { + notifyOnly: propTypes.bool, + initialState: propTypes.object, +}; + +ReactDOM.render( + , + document.getElementById('content'), +); diff --git a/src/utils/url.js b/src/utils/url.js deleted file mode 100644 index dfd177cf..00000000 --- a/src/utils/url.js +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {isHttpsUri, isHttpUri, isUri} from 'valid-url'; - -import buildConfig from '../common/config/buildConfig'; - -function getDomain(inputURL) { - const parsedURL = parseURL(inputURL); - return parsedURL.origin; -} - -function isValidURL(testURL) { - return Boolean(isHttpUri(testURL) || isHttpsUri(testURL)) && parseURL(testURL) !== null; -} - -function isValidURI(testURL) { - return Boolean(isUri(testURL)); -} - -function parseURL(inputURL) { - if (!inputURL) { - return null; - } - if (inputURL instanceof URL) { - return inputURL; - } - try { - return new URL(inputURL); - } catch (e) { - return null; - } -} - -function getHost(inputURL) { - const parsedURL = parseURL(inputURL); - if (parsedURL) { - return parsedURL.origin; - } - throw new Error(`Couldn't parse url: ${inputURL}`); -} - -// isInternalURL determines if the target url is internal to the application. -// - currentURL is the current url inside the webview -// - basename is the global export from the Mattermost application defining the subpath, if any -function isInternalURL(targetURL, currentURL, basename = '/') { - if (targetURL.host !== currentURL.host) { - return false; - } - - if (!(targetURL.pathname || '/').startsWith(basename)) { - return false; - } - - return true; -} - -function getServerInfo(serverUrl) { - const parsedServer = parseURL(serverUrl); - if (!parsedServer) { - return null; - } - - // does the server have a subpath? - const pn = parsedServer.pathname.toLowerCase(); - const subpath = pn.endsWith('/') ? pn.toLowerCase() : `${pn}/`; - return {origin: parsedServer.origin, subpath, url: parsedServer}; -} - -function getManagedResources() { - if (!buildConfig) { - return []; - } - - return buildConfig.managedResources || []; -} - -function isAdminUrl(serverUrl, inputUrl) { - const parsedURL = parseURL(inputUrl); - const server = getServerInfo(serverUrl); - if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) { - return null; - } - return (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}/admin_console/`) || - parsedURL.pathname.toLowerCase().startsWith('/admin_console/')); -} - -function isTeamUrl(serverUrl, inputUrl, withApi) { - const parsedURL = parseURL(inputUrl); - const server = getServerInfo(serverUrl); - if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server, parsedURL))) { - return null; - } - - // pre process nonTeamUrlPaths - let nonTeamUrlPaths = [ - 'plugins', - 'signup', - 'login', - 'admin', - 'channel', - 'post', - 'oauth', - 'admin_console', - ]; - const managedResources = getManagedResources(); - nonTeamUrlPaths = nonTeamUrlPaths.concat(managedResources); - - if (withApi) { - nonTeamUrlPaths.push('api'); - } - return !(nonTeamUrlPaths.some((testPath) => ( - parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${testPath}/`) || - parsedURL.pathname.toLowerCase().startsWith(`/${testPath}/`)))); -} - -function isPluginUrl(serverUrl, inputURL) { - const server = getServerInfo(serverUrl); - const parsedURL = parseURL(inputURL); - if (!parsedURL || !server) { - return false; - } - return ( - equalUrlsIgnoringSubpath(server, parsedURL) && - (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}plugins/`) || - parsedURL.pathname.toLowerCase().startsWith('/plugins/'))); -} - -function isManagedResource(serverUrl, inputURL) { - const server = getServerInfo(serverUrl); - const parsedURL = parseURL(inputURL); - if (!parsedURL || !server) { - return false; - } - - const managedResources = getManagedResources(); - - return ( - equalUrlsIgnoringSubpath(server, parsedURL) && managedResources && managedResources.length && - managedResources.some((managedResource) => (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${managedResource}/`) || parsedURL.pathname.toLowerCase().startsWith(`/${managedResource}/`)))); -} - -function getServer(inputURL, teams) { - const parsedURL = parseURL(inputURL); - if (!parsedURL) { - return null; - } - let parsedServerUrl; - let secondOption = null; - for (let i = 0; i < teams.length; i++) { - parsedServerUrl = parseURL(teams[i].url); - - // check server and subpath matches (without subpath pathname is \ so it always matches) - if (equalUrlsWithSubpath(parsedServerUrl, parsedURL)) { - return {name: teams[i].name, url: parsedServerUrl, index: i}; - } - if (equalUrlsIgnoringSubpath(parsedServerUrl, parsedURL)) { - // in case the user added something on the path that doesn't really belong to the server - // there might be more than one that matches, but we can't differentiate, so last one - // is as good as any other in case there is no better match (e.g.: two subpath servers with the same origin) - // e.g.: https://community.mattermost.com/core - secondOption = {name: teams[i].name, url: parsedServerUrl, index: i}; - } - } - return secondOption; -} - -// next two functions are defined to clarify intent -function equalUrlsWithSubpath(url1, url2) { - return url1.origin === url2.origin && url2.pathname.toLowerCase().startsWith(url1.pathname.toLowerCase()); -} - -function equalUrlsIgnoringSubpath(url1, url2) { - return url1.origin.toLowerCase() === url2.origin.toLowerCase(); -} - -export default { - getDomain, - isValidURL, - isValidURI, - isInternalURL, - parseURL, - getServer, - getServerInfo, - isAdminUrl, - isTeamUrl, - isPluginUrl, - isManagedResource, - getHost, -}; diff --git a/src/utils/util.js b/src/utils/util.js deleted file mode 100644 index 5f262693..00000000 --- a/src/utils/util.js +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import electron, {remote} from 'electron'; -import log from 'electron-log'; - -function getDisplayBoundaries() { - const {screen} = electron; - - const displays = screen.getAllDisplays(); - - return displays.map((display) => { - return { - maxX: display.workArea.x + display.workArea.width, - maxY: display.workArea.y + display.workArea.height, - minX: display.workArea.x, - minY: display.workArea.y, - maxWidth: display.workArea.width, - maxHeight: display.workArea.height, - }; - }); -} - -const dispatchNotification = async (title, body, silent, data, handleClick) => { - let permission; - const appIconURL = `file:///${remote.app.getAppPath()}/assets/appicon_48.png`; - if (Notification.permission === 'default') { - permission = await Notification.requestPermission(); - } else { - permission = Notification.permission; - } - - if (permission !== 'granted') { - log.error('Notifications not granted'); - return null; - } - - const notification = new Notification(title, { - body, - tag: body, - icon: appIconURL, - requireInteraction: false, - silent, - data, - }); - - notification.onclick = handleClick; - - notification.onerror = () => { - log.error('Notification failed to show'); - }; - - return notification; -}; - -export default { - getDisplayBoundaries, - dispatchNotification, -}; diff --git a/test/modules/environment.js b/test/modules/environment.js index f646696f..6d893397 100644 --- a/test/modules/environment.js +++ b/test/modules/environment.js @@ -4,19 +4,23 @@ 'use strict'; const fs = require('fs'); + const path = require('path'); const Application = require('spectron').Application; const chai = require('chai'); +const {ipcRenderer} = require('electron'); + +const {SHOW_SETTINGS_WINDOW} = require('../../src/common/communication'); chai.should(); const sourceRootDir = path.join(__dirname, '../..'); const electronBinaryPath = (() => { - if (process.platform === 'darwin') { - return path.join(sourceRootDir, 'node_modules/electron/dist/Electron.app/Contents/MacOS/Electron'); - } - const exeExtension = (process.platform === 'win32') ? '.exe' : ''; - return path.join(sourceRootDir, 'node_modules/electron/dist/electron' + exeExtension); + if (process.platform === 'darwin') { + return path.join(sourceRootDir, 'node_modules/electron/dist/Electron.app/Contents/MacOS/Electron'); + } + const exeExtension = (process.platform === 'win32') ? '.exe' : ''; + return path.join(sourceRootDir, 'node_modules/electron/dist/electron' + exeExtension); })(); const userDataDir = path.join(sourceRootDir, 'test/testUserData/'); const configFilePath = path.join(userDataDir, 'config.json'); @@ -24,79 +28,81 @@ const boundsInfoPath = path.join(userDataDir, 'bounds-info.json'); const mattermostURL = 'http://example.com/'; module.exports = { - sourceRootDir, - configFilePath, - userDataDir, - boundsInfoPath, - mattermostURL, + sourceRootDir, + configFilePath, + userDataDir, + boundsInfoPath, + mattermostURL, - cleanTestConfig() { - [configFilePath, boundsInfoPath].forEach((file) => { - try { - fs.unlinkSync(file); - } catch (err) { - if (err.code !== 'ENOENT') { - console.error(err); + cleanTestConfig() { + [configFilePath, boundsInfoPath].forEach((file) => { + try { + fs.unlinkSync(file); + } catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.error(err); + } + } + }); + }, + + createTestUserDataDir() { + if (!fs.existsSync(userDataDir)) { + fs.mkdirSync(userDataDir); } - } - }); - }, + }, - createTestUserDataDir() { - if (!fs.existsSync(userDataDir)) { - fs.mkdirSync(userDataDir); - } - }, - - getSpectronApp() { - const options = { - path: electronBinaryPath, - args: [`${path.join(sourceRootDir, 'src')}`, `--data-dir=${userDataDir}`, '--disable-dev-mode'], - chromeDriverArgs: [], - - // enable this if chromedriver hangs to see logs - // chromeDriverLogPath: '../chromedriverlog.txt', - }; - if (process.platform === 'darwin' || process.platform === 'linux') { - // on a mac, debbuging port might conflict with other apps - // this changes the default debugging port so chromedriver can run without issues. - options.chromeDriverArgs.push('remote-debugging-port=9222'); - } - return new Application(options); - }, - - addClientCommands(client) { - client.addCommand('loadSettingsPage', function async() { - return this.url('file://' + path.join(sourceRootDir, 'src/browser/settings.html')).waitUntilWindowLoaded(); - }); - client.addCommand('isNodeEnabled', function async() { - return this.execute(() => { - try { - if (require('child_process')) { - return true; - } - return false; - } catch (e) { - return false; + getSpectronApp() { + const options = { + path: electronBinaryPath, + args: [`${path.join(sourceRootDir, 'dist')}`, `--data-dir=${userDataDir}`, '--disable-dev-mode'], + chromeDriverArgs: [], + }; + if (process.env.MM_DEBUG_SETTINGS) { + options.chromeDriverLogPath = './chromedriverlog.txt'; } - }).then((requireResult) => { - return requireResult.value; - }); - }); - client.addCommand('waitForAppOptionsAutoSaved', function async() { - const ID_APP_OPTIONS_SAVE_INDICATOR = '#appOptionsSaveIndicator'; - const TIMEOUT = 5000; - return this. - waitForVisible(ID_APP_OPTIONS_SAVE_INDICATOR, TIMEOUT). - waitForVisible(ID_APP_OPTIONS_SAVE_INDICATOR, TIMEOUT, true); - }); - }, + if (process.platform === 'darwin' || process.platform === 'linux') { + // on a mac, debbuging port might conflict with other apps + // this changes the default debugging port so chromedriver can run without issues. + options.chromeDriverArgs.push('remote-debugging-port=9222'); + } + return new Application(options); + }, - // execute the test only when `condition` is true - shouldTest(it, condition) { - return condition ? it : it.skip; - }, - isOneOf(platforms) { - return (platforms.indexOf(process.platform) !== -1); - }, + addClientCommands(client) { + client.addCommand('loadSettingsPage', function async() { + ipcRenderer.send(SHOW_SETTINGS_WINDOW); + }); + client.addCommand('isNodeEnabled', function async() { + return this.execute(() => { + try { + if (require('child_process')) { + return true; + } + return false; + } catch (e) { + return false; + } + }).then((requireResult) => { + return requireResult.value; + }); + }); + client.addCommand('waitForAppOptionsAutoSaved', function async() { + const ID_APP_OPTIONS_SAVE_INDICATOR = '#appOptionsSaveIndicator'; + const TIMEOUT = 5000; + return this. + waitForVisible(ID_APP_OPTIONS_SAVE_INDICATOR, TIMEOUT). + waitForVisible(ID_APP_OPTIONS_SAVE_INDICATOR, TIMEOUT, true); + }); + }, + + // execute the test only when `condition` is true + shouldTest(it, condition) { + // eslint-disable-next-line no-only-tests/no-only-tests + return condition ? it : it.skip; + }, + isOneOf(platforms) { + return (platforms.indexOf(process.platform) !== -1); + }, }; diff --git a/test/modules/utils.js b/test/modules/utils.js index 53609ca3..1f417289 100644 --- a/test/modules/utils.js +++ b/test/modules/utils.js @@ -3,13 +3,13 @@ // See LICENSE.txt for license information. function asyncSleep(timeout) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, timeout); - }); + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, timeout); + }); } module.exports = { - asyncSleep, + asyncSleep, }; diff --git a/test/specs/app_test.js b/test/specs/app_test.js index f92066b7..39d770b0 100644 --- a/test/specs/app_test.js +++ b/test/specs/app_test.js @@ -8,145 +8,144 @@ const fs = require('fs'); const env = require('../modules/environment'); describe('application', function desc() { - this.timeout(30000); + this.timeout(30000); - beforeEach(() => { - env.createTestUserDataDir(); - env.cleanTestConfig(); - this.app = env.getSpectronApp(); - return this.app.start(); - }); - - afterEach(async () => { - if (this.app && this.app.isRunning()) { - await this.app.stop(); - } - }); - - it('should show a window', async () => { - await this.app.client.waitUntilWindowLoaded(); - const count = await this.app.client.getWindowCount(); - count.should.equal(1); - const opened = await this.app.browserWindow.isDevToolsOpened(); - opened.should.be.false; - - const visible = await this.app.browserWindow.isVisible(); - visible.should.be.true; - }); - - if (process.platform === 'darwin') { - it.skip('should show closed window with cmd+tab', async () => { - // Unable to utilize Command key press due to: https://bugs.chromium.org/p/chromedriver/issues/detail?id=3023#c2 - await this.app.client.waitUntilWindowLoaded(); - await this.app.client.keys(['Meta', 'w']); - let visible = await this.app.browserWindow.isVisible(); - visible.should.be.false; - - this.app.client.keys(['Meta', 'Tab']); - visible = await this.app.browserWindow.isVisible(); - visible.should.be.true; + beforeEach(() => { + env.createTestUserDataDir(); + env.cleanTestConfig(); + this.app = env.getSpectronApp(); + return this.app.start(); }); - } - it.skip('should restore window bounds', async () => { - // bounds seems to be incorrectly calculated in some environments - // - Windows 10: OK - // - CircleCI: NG - const expectedBounds = {x: 100, y: 200, width: 300, height: 400}; - fs.writeFileSync(env.boundsInfoPath, JSON.stringify(expectedBounds)); - await this.app.restart(); - const bounds = await this.app.browserWindow.getBounds(); - bounds.should.deep.equal(expectedBounds); - }); + afterEach(async () => { + if (this.app && this.app.isRunning()) { + await this.app.stop(); + } + }); - it('should NOT restore window bounds if the origin is located on outside of viewarea', async () => { + it('should show two windows if there is no config file', async () => { + await this.app.client.waitUntilWindowLoaded(); + const count = await this.app.client.getWindowCount(); + count.should.equal(2); + const opened = await this.app.browserWindow.isDevToolsOpened(); + opened.should.be.false; + + const visible = await this.app.browserWindow.isVisible(); + visible.should.be.true; + }); + + if (process.platform === 'darwin') { + it.skip('should show closed window with cmd+tab', async () => { + // Unable to utilize Command key press due to: https://bugs.chromium.org/p/chromedriver/issues/detail?id=3023#c2 + await this.app.client.waitUntilWindowLoaded(); + await this.app.client.keys(['Meta', 'w']); + let visible = await this.app.browserWindow.isVisible(); + visible.should.be.false; + + this.app.client.keys(['Meta', 'Tab']); + visible = await this.app.browserWindow.isVisible(); + visible.should.be.true; + }); + } + + it.skip('should restore window bounds', async () => { + // bounds seems to be incorrectly calculated in some environments + // - Windows 10: OK + // - CircleCI: NG + const expectedBounds = {x: 100, y: 200, width: 300, height: 400}; + fs.writeFileSync(env.boundsInfoPath, JSON.stringify(expectedBounds)); + await this.app.restart(); + const bounds = await this.app.browserWindow.getBounds(); + bounds.should.deep.equal(expectedBounds); + }); + + it('should NOT restore window bounds if the origin is located on outside of viewarea', async () => { // bounds seems to be incorrectly calculated in some environments (e.g. CircleCI) // - Windows 10: OK // - CircleCI: NG - fs.writeFileSync(env.boundsInfoPath, JSON.stringify({x: -100000, y: 200, width: 300, height: 400})); - await this.app.restart(); - let bounds = await this.app.browserWindow.getBounds(); - bounds.x.should.satisfy((x) => (x > -10000)); + fs.writeFileSync(env.boundsInfoPath, JSON.stringify({x: -100000, y: 200, width: 300, height: 400})); + await this.app.restart(); + let bounds = await this.app.browserWindow.getBounds(); + bounds.x.should.satisfy((x) => (x > -10000)); - fs.writeFileSync(env.boundsInfoPath, JSON.stringify({x: 100, y: 200000, width: 300, height: 400})); - await this.app.restart(); - bounds = await this.app.browserWindow.getBounds(); - bounds.y.should.satisfy((y) => (y < 10000)); - }); - - it('should show settings.html when there is no config file', async () => { - await this.app.client.waitUntilWindowLoaded(); - await this.app.client.pause(1000); - const url = await this.app.client.getUrl(); - url.should.match(/\/settings.html$/); - - const existing = await this.app.client.isExisting('#newServerModal'); - existing.should.equal(true); - }); - - it('should show index.html when there is config file', async () => { - const config = { - version: 2, - teams: [{ - name: 'example', - url: env.mattermostURL, - order: 0, - }, { - name: 'github', - url: 'https://github.com/', - order: 1, - }], - showTrayIcon: false, - trayIconTheme: 'light', - minimizeToTray: false, - notifications: { - flashWindow: 0, - bounceIcon: false, - bounceIconType: 'informational', - }, - showUnreadBadge: true, - useSpellChecker: true, - enableHardwareAcceleration: true, - autostart: true, - darkMode: false, - }; - fs.writeFileSync(env.configFilePath, JSON.stringify(config)); - await this.app.restart(); - - const url = await this.app.client.getUrl(); - url.should.match(/\/index.html$/); - }); - - it('should upgrade v0 config file', async () => { - const Config = require('../../src/common/config').default; - const newConfig = new Config(env.configFilePath); - const oldConfig = { - url: env.mattermostURL, - }; - fs.writeFileSync(env.configFilePath, JSON.stringify(oldConfig)); - await this.app.restart(); - - const url = await this.app.client.getUrl(); - url.should.match(/\/index.html$/); - - const str = fs.readFileSync(env.configFilePath, 'utf8'); - const upgradedConfig = JSON.parse(str); - upgradedConfig.version.should.equal(newConfig.defaultData.version); - }); - - it.skip('should be stopped when the app instance already exists', (done) => { - const secondApp = env.getSpectronApp(); - - // In the correct case, 'start().then' is not called. - // So need to use setTimeout in order to finish this test. - const timer = setTimeout(() => { - done(); - }, 3000); - secondApp.start().then(() => { - clearTimeout(timer); - return secondApp.stop(); - }).then(() => { - done(new Error('Second app instance exists')); + fs.writeFileSync(env.boundsInfoPath, JSON.stringify({x: 100, y: 200000, width: 300, height: 400})); + await this.app.restart(); + bounds = await this.app.browserWindow.getBounds(); + bounds.y.should.satisfy((y) => (y < 10000)); }); - }); + + // it('should show settings.html when there is no config file', async () => { + // await this.app.client.waitUntilWindowLoaded(); + // await this.app.client.pause(1000); + // const url = await this.app.client.getUrl(); + // url.should.match(/\/settings.html$/); + + // const existing = await this.app.client.isExisting('#newServerModal'); + // existing.should.equal(true); + // }); + + // it('should show index.html when there is config file', async () => { + // const config = { + // version: 2, + // teams: [{ + // name: 'example', + // url: env.mattermostURL, + // order: 0, + // }, { + // name: 'github', + // url: 'https://github.com/', + // order: 1, + // }], + // showTrayIcon: false, + // trayIconTheme: 'light', + // minimizeToTray: false, + // notifications: { + // flashWindow: 0, + // bounceIcon: false, + // bounceIconType: 'informational', + // }, + // showUnreadBadge: true, + // useSpellChecker: true, + // enableHardwareAcceleration: true, + // autostart: true, + // darkMode: false, + // }; + // fs.writeFileSync(env.configFilePath, JSON.stringify(config)); + // await this.app.restart(); + + // const url = await this.app.client.getUrl(); + // url.should.match(/\/index.html$/); + // }); + + // it('should upgrade v0 config file', async () => { + // const Config = require('../../src/common/config').default; + // const newConfig = new Config(env.configFilePath); + // const oldConfig = { + // url: env.mattermostURL, + // }; + // fs.writeFileSync(env.configFilePath, JSON.stringify(oldConfig)); + // await this.app.restart(); + + // const url = await this.app.client.getUrl(); + // url.should.match(/\/index.html$/); + + // const str = fs.readFileSync(env.configFilePath, 'utf8'); + // const upgradedConfig = JSON.parse(str); + // upgradedConfig.version.should.equal(newConfig.defaultData.version); + // }); + + // it.skip('should be stopped when the app instance already exists', (done) => { + // const secondApp = env.getSpectronApp(); + + // // In the correct case, 'start().then' is not called. + // // So need to use setTimeout in order to finish this test. + // const timer = setTimeout(() => { + // done(); + // }, 3000); + // secondApp.start().then(() => { + // clearTimeout(timer); + // return secondApp.stop(); + // }).then(() => { + // done(new Error('Second app instance exists')); + // }); }); diff --git a/test/specs/browser/index_test.js b/test/specs/browser/index_test.js index bc584a05..ed701eb8 100644 --- a/test/specs/browser/index_test.js +++ b/test/specs/browser/index_test.js @@ -4,211 +4,212 @@ 'use strict'; const fs = require('fs'); + const http = require('http'); const path = require('path'); const env = require('../../modules/environment'); const {asyncSleep} = require('../../modules/utils'); -describe('browser/index.html', function desc() { - this.timeout(30000); - - const config = { - version: 2, - teams: [{ - name: 'example', - url: env.mattermostURL, - order: 0, - }, { - name: 'github', - url: 'https://github.com/', - order: 1, - }], - showTrayIcon: false, - trayIconTheme: 'light', - minimizeToTray: false, - notifications: { - flashWindow: 0, - bounceIcon: false, - bounceIconType: 'informational', - }, - showUnreadBadge: true, - useSpellChecker: true, - enableHardwareAcceleration: true, - autostart: true, - darkMode: false, - }; - - const serverPort = 8181; - - before(() => { - function serverCallback(req, res) { - res.writeHead(200, { - 'Content-Type': 'text/html', - }); - res.end(fs.readFileSync(path.resolve(env.sourceRootDir, 'test/modules/test.html'), 'utf-8')); - } - this.server = http.createServer(serverCallback).listen(serverPort, '127.0.0.1'); - }); - - beforeEach(async () => { - fs.writeFileSync(env.configFilePath, JSON.stringify(config)); - await asyncSleep(1000); - this.app = env.getSpectronApp(); - await this.app.start(); - }); - - afterEach(async () => { - if (this.app && this.app.isRunning()) { - await this.app.stop(); - } - }); - - after((done) => { - this.server.close(done); - }); - - it('should set src of webview from config file', async () => { - const src0 = await this.app.client.getAttribute('#mattermostView0', 'src'); - src0.should.equal(config.teams[0].url); - - const src1 = await this.app.client.getAttribute('#mattermostView1', 'src'); - src1.should.equal(config.teams[1].url); - - const existing = await this.app.client.isExisting('#mattermostView2'); - existing.should.be.false; - }); - - it('should set name of tab from config file', async () => { - const tabName0 = await this.app.client.getText('#teamTabItem0'); - tabName0.should.equal(config.teams[0].name); - - const tabName1 = await this.app.client.getText('#teamTabItem1'); - tabName1.should.equal(config.teams[1].name); - }); - - it('should show only the selected team', () => { - return this.app.client. - waitForVisible('#mattermostView0', 2000). - waitForVisible('#mattermostView1', 2000, true). - click('#teamTabItem1'). - waitForVisible('#mattermostView1', 2000). - waitForVisible('#mattermostView0', 2000, true); - }); - - // validation now prevents incorrect url's from being used - it.skip('should show error when using incorrect URL', async () => { +describe('renderer/index.html', function desc() { this.timeout(30000); - fs.writeFileSync(env.configFilePath, JSON.stringify({ - version: 2, - teams: [{ - name: 'error_1', - url: 'http://false', - order: 0, - }], - })); - await this.app.restart(); - return this.app.client. - waitForVisible('#mattermostView0-fail', 20000); - }); - it('should set window title by using webview\'s one', async () => { - fs.writeFileSync(env.configFilePath, JSON.stringify({ - version: 2, - teams: [{ - name: 'title_test', - url: `http://localhost:${serverPort}`, - order: 0, - }], - })); - await this.app.restart(); - await this.app.client.pause(2000); - const windowTitle = await this.app.browserWindow.getTitle(); - windowTitle.should.equal('Mattermost Desktop testing html'); - }); + const config = { + version: 2, + teams: [{ + name: 'example', + url: env.mattermostURL, + order: 0, + }, { + name: 'github', + url: 'https://github.com/', + order: 1, + }], + showTrayIcon: false, + trayIconTheme: 'light', + minimizeToTray: false, + notifications: { + flashWindow: 0, + bounceIcon: false, + bounceIconType: 'informational', + }, + showUnreadBadge: true, + useSpellChecker: true, + enableHardwareAcceleration: true, + autostart: true, + darkMode: false, + }; - // Skip because it's very unstable in CI - it.skip('should update window title when the activated tab\'s title is updated', async () => { - fs.writeFileSync(env.configFilePath, JSON.stringify({ - version: 2, - teams: [{ - name: 'title_test_0', - url: `http://localhost:${serverPort}`, - order: 0, - }, { - name: 'title_test_1', - url: `http://localhost:${serverPort}`, - order: 1, - }], - })); - await this.app.restart(); - await this.app.client.pause(500); + const serverPort = 8181; - // Note: Indices of webview are correct. - // Somehow they are swapped. - await this.app.client. - windowByIndex(2). - execute(() => { - document.title = 'Title 0'; - }); - await this.app.client.windowByIndex(0).pause(500); - let windowTitle = await this.app.browserWindow.getTitle(); - windowTitle.should.equal('Title 0'); + before(() => { + function serverCallback(req, res) { + res.writeHead(200, { + 'Content-Type': 'text/html', + }); + res.end(fs.readFileSync(path.resolve(env.sourceRootDir, 'test/modules/test.html'), 'utf-8')); + } + this.server = http.createServer(serverCallback).listen(serverPort, '127.0.0.1'); + }); - await this.app.client. - windowByIndex(1). - execute(() => { - document.title = 'Title 1'; - }); - await this.app.client.windowByIndex(0).pause(500); - windowTitle = await this.app.browserWindow.getTitle(); - windowTitle.should.equal('Title 0'); - }); + beforeEach(async () => { + fs.writeFileSync(env.configFilePath, JSON.stringify(config)); + await asyncSleep(1000); + this.app = env.getSpectronApp(); + await this.app.start(); + }); - // Skip because it's very unstable in CI - it.skip('should update window title when a tab is selected', async () => { - fs.writeFileSync(env.configFilePath, JSON.stringify({ - version: 2, - teams: [{ - name: 'title_test_0', - url: `http://localhost:${serverPort}`, - order: 0, - }, { - name: 'title_test_1', - url: `http://localhost:${serverPort}`, - order: 1, - }], - })); - await this.app.restart(); + afterEach(async () => { + if (this.app && this.app.isRunning()) { + await this.app.stop(); + } + }); - // Note: Indices of webview are correct. - // Somehow they are swapped. - await this.app.client.pause(500); + after((done) => { + this.server.close(done); + }); - await this.app.client. - windowByIndex(2). - execute(() => { - document.title = 'Title 0'; - }); - await this.app.client. - windowByIndex(1). - execute(() => { - document.title = 'Title 1'; - }); - await this.app.client.windowByIndex(0).pause(500); + // it('should set src of webview from config file', async () => { + // const src0 = await this.app.client.getAttribute('#mattermostView0', 'src'); + // src0.should.equal(config.teams[0].url); - let windowTitle = await this.app.browserWindow.getTitle(); - windowTitle.should.equal('Title 0'); + // const src1 = await this.app.client.getAttribute('#mattermostView1', 'src'); + // src1.should.equal(config.teams[1].url); - await this.app.client.click('#teamTabItem1').pause(500); - windowTitle = await this.app.browserWindow.getTitle(); - windowTitle.should.equal('Title 1'); - }); + // const existing = await this.app.client.isExisting('#mattermostView2'); + // existing.should.be.false; + // }); - it('should open the new server prompt after clicking the add button', async () => { - // See settings_test for specs that cover the actual prompt - await this.app.client.click('#addServerButton').pause(500); - const isModalExisting = await this.app.client.isExisting('#newServerModal'); - isModalExisting.should.be.true; - }); + // it('should set name of tab from config file', async () => { + // const tabName0 = await this.app.client.getText('#teamTabItem0'); + // tabName0.should.equal(config.teams[0].name); + + // const tabName1 = await this.app.client.getText('#teamTabItem1'); + // tabName1.should.equal(config.teams[1].name); + // }); + + // it('should show only the selected team', () => { + // return this.app.client. + // waitForVisible('#mattermostView0', 2000). + // waitForVisible('#mattermostView1', 2000, true). + // click('#teamTabItem1'). + // waitForVisible('#mattermostView1', 2000). + // waitForVisible('#mattermostView0', 2000, true); + // }); + + // validation now prevents incorrect url's from being used + // it.skip('should show error when using incorrect URL', async () => { + // this.timeout(30000); + // fs.writeFileSync(env.configFilePath, JSON.stringify({ + // version: 2, + // teams: [{ + // name: 'error_1', + // url: 'http://false', + // order: 0, + // }], + // })); + // await this.app.restart(); + // return this.app.client. + // waitForVisible('#mattermostView0-fail', 20000); + // }); + + it('shouldn\'t set window title by using webview\'s one', async () => { + fs.writeFileSync(env.configFilePath, JSON.stringify({ + version: 2, + teams: [{ + name: 'title_test', + url: `http://localhost:${serverPort}`, + order: 0, + }], + })); + await this.app.restart(); + await this.app.client.pause(2000); + const windowTitle = await this.app.browserWindow.getTitle(); + windowTitle.should.equal('Mattermost Desktop App'); + }); + + // Skip because it's very unstable in CI + // it.skip('should update window title when the activated tab\'s title is updated', async () => { + // fs.writeFileSync(env.configFilePath, JSON.stringify({ + // version: 2, + // teams: [{ + // name: 'title_test_0', + // url: `http://localhost:${serverPort}`, + // order: 0, + // }, { + // name: 'title_test_1', + // url: `http://localhost:${serverPort}`, + // order: 1, + // }], + // })); + // await this.app.restart(); + // await this.app.client.pause(500); + + // // Note: Indices of webview are correct. + // // Somehow they are swapped. + // await this.app.client. + // windowByIndex(2). + // execute(() => { + // document.title = 'Title 0'; + // }); + // await this.app.client.windowByIndex(0).pause(500); + // let windowTitle = await this.app.browserWindow.getTitle(); + // windowTitle.should.equal('Title 0'); + + // await this.app.client. + // windowByIndex(1). + // execute(() => { + // document.title = 'Title 1'; + // }); + // await this.app.client.windowByIndex(0).pause(500); + // windowTitle = await this.app.browserWindow.getTitle(); + // windowTitle.should.equal('Title 0'); + // }); + + // Skip because it's very unstable in CI + // it.skip('should update window title when a tab is selected', async () => { + // fs.writeFileSync(env.configFilePath, JSON.stringify({ + // version: 2, + // teams: [{ + // name: 'title_test_0', + // url: `http://localhost:${serverPort}`, + // order: 0, + // }, { + // name: 'title_test_1', + // url: `http://localhost:${serverPort}`, + // order: 1, + // }], + // })); + // await this.app.restart(); + + // // Note: Indices of webview are correct. + // // Somehow they are swapped. + // await this.app.client.pause(500); + + // await this.app.client. + // windowByIndex(2). + // execute(() => { + // document.title = 'Title 0'; + // }); + // await this.app.client. + // windowByIndex(1). + // execute(() => { + // document.title = 'Title 1'; + // }); + // await this.app.client.windowByIndex(0).pause(500); + + // let windowTitle = await this.app.browserWindow.getTitle(); + // windowTitle.should.equal('Title 0'); + + // await this.app.client.click('#teamTabItem1').pause(500); + // windowTitle = await this.app.browserWindow.getTitle(); + // windowTitle.should.equal('Title 1'); + // }); + + // it('should open the new server prompt after clicking the add button', async () => { + // // See settings_test for specs that cover the actual prompt + // await this.app.client.click('#addServerButton').pause(500); + // const isModalExisting = await this.app.client.isExisting('#newServerModal'); + // isModalExisting.should.be.true; + // }); }); diff --git a/test/specs/browser/settings_test.js b/test/specs/browser/settings_test.js index 45c1c93d..6ed7c78e 100644 --- a/test/specs/browser/settings_test.js +++ b/test/specs/browser/settings_test.js @@ -1,529 +1,532 @@ // Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -'use strict'; - -const fs = require('fs'); - -const env = require('../../modules/environment'); -const {asyncSleep} = require('../../modules/utils'); - -describe('browser/settings.html', function desc() { - this.timeout(30000); - - const config = { - version: 2, - teams: [{ - name: 'example', - url: env.mattermostURL, - order: 0, - }, { - name: 'github', - url: 'https://github.com/', - order: 1, - }], - showTrayIcon: false, - trayIconTheme: 'light', - minimizeToTray: false, - notifications: { - flashWindow: 0, - bounceIcon: false, - bounceIconType: 'informational', - }, - showUnreadBadge: true, - useSpellChecker: true, - enableHardwareAcceleration: true, - autostart: true, - darkMode: false, - }; - - beforeEach(async () => { - fs.writeFileSync(env.configFilePath, JSON.stringify(config)); - await asyncSleep(1000); - this.app = env.getSpectronApp(); - await this.app.start(); - }); - - afterEach(async () => { - if (this.app && this.app.isRunning()) { - await this.app.stop(); - } - }); - - describe('Close button', async () => { - it.skip('should show index.html when it\'s clicked', async () => { - env.addClientCommands(this.app.client); - await this.app.client. - loadSettingsPage(). - click('#btnClose'). - pause(1000); - const url = await this.app.client.getUrl(); - url.should.match(/\/index.html(\?.+)?$/); - }); - - it('should be disabled when the number of servers is zero', async () => { - await this.app.stop(); - env.cleanTestConfig(); - await this.app.start(); - - await this.app.client.waitUntilWindowLoaded(). - waitForVisible('#newServerModal', 10000). - click('#cancelNewServerModal'); - let isCloseButtonEnabled = await this.app.client.isEnabled('#btnClose'); - isCloseButtonEnabled.should.equal(false); - - await this.app.client. - waitForVisible('#newServerModal', true). - pause(250). - click('#addNewServer'). - waitForVisible('#newServerModal'). - setValue('#teamNameInput', 'TestTeam'). - pause(100). - setValue('#teamUrlInput', 'http://example.org'). - click('#saveNewServerModal'). - waitForVisible('#newServerModal', true). - waitForVisible('#serversSaveIndicator'). - waitForVisible('#serversSaveIndicator', 10000, true); // at least 2500 ms to disappear - isCloseButtonEnabled = await this.app.client.isEnabled('#btnClose'); - isCloseButtonEnabled.should.equal(true); - }); - }); - - it('should show NewServerModal after all servers are removed', async () => { - const modalTitleSelector = '.modal-title=Remove Server'; - env.addClientCommands(this.app.client); - await this.app.client. - loadSettingsPage(). - click('=Remove'). - waitForVisible(modalTitleSelector). - element('.modal-dialog').click('.btn=Remove'). - pause(500). - click('=Remove'). - waitForVisible(modalTitleSelector). - element('.modal-dialog').click('.btn=Remove'). - pause(500); - const isModalExisting = await this.app.client.isExisting('#newServerModal'); - isModalExisting.should.be.true; - }); - - describe('Server list', () => { - it.skip('should open the corresponding tab when a server list item is clicked', async () => { - env.addClientCommands(this.app.client); - await this.app.client. - loadSettingsPage(). - click('h4=example'). - pause(1000). - waitUntilWindowLoaded(); - let indexURL = await this.app.client.getUrl(); - indexURL.should.match(/\/index.html(\?.+)?$/); - - let isView0Visible = await this.app.client.isVisible('#mattermostView0'); - isView0Visible.should.be.true; - - let isView1Visible = await this.app.client.isVisible('#mattermostView1'); - isView1Visible.should.be.false; - - await this.app.client. - loadSettingsPage(). - click('h4=github'). - pause(1000). - waitUntilWindowLoaded(); - indexURL = await this.app.client.getUrl(); - indexURL.should.match(/\/index.html(\?.+)?$/); - - isView0Visible = await this.app.client.isVisible('#mattermostView0'); - isView0Visible.should.be.false; - - isView1Visible = await this.app.client.isVisible('#mattermostView1'); - isView1Visible.should.be.true; - }); - }); - - describe('Options', () => { - describe.skip('Hide Menu Bar', () => { - it('should appear on win32 or linux', async () => { - const expected = (process.platform === 'win32' || process.platform === 'linux'); - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputHideMenuBar'); - existing.should.equal(expected); - }); - - [true, false].forEach((v) => { - env.shouldTest(it, env.isOneOf(['win32', 'linux']))(`should be saved and loaded: ${v}`, async () => { - env.addClientCommands(this.app.client); - await this.app.client. - loadSettingsPage(). - scroll('#inputHideMenuBar'); - const isSelected = await this.app.client.isSelected('#inputHideMenuBar'); - if (isSelected !== v) { - await this.app.client.click('#inputHideMenuBar'); - } - - await this.app.client. - pause(600). - click('#btnClose'). - pause(1000); - - const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); - savedConfig.hideMenuBar.should.equal(v); - - let autoHide = await this.app.browserWindow.isMenuBarAutoHide(); - autoHide.should.equal(v); - - // confirm actual behavior - await this.app.restart(); - env.addClientCommands(this.app.client); - - autoHide = await this.app.browserWindow.isMenuBarAutoHide(); - autoHide.should.equal(v); - - await this.app.loadSettingsPage(); - autoHide = await this.app.client.isSelected('#inputHideMenuBar'); - autoHide.should.equal(v); - }); - }); - }); - - describe('Start app on login', () => { - it('should appear on win32 or linux', async () => { - const expected = (process.platform === 'win32' || process.platform === 'linux'); - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputAutoStart'); - existing.should.equal(expected); - }); - }); - - describe('Show icon in menu bar / notification area', () => { - it('should appear on darwin or linux', async () => { - const expected = (process.platform === 'darwin' || process.platform === 'linux'); - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputShowTrayIcon'); - existing.should.equal(expected); - }); - - describe('Save tray icon setting on mac', () => { - env.shouldTest(it, env.isOneOf(['darwin', 'linux']))('should be saved when it\'s selected', async () => { - env.addClientCommands(this.app.client); - await this.app.browserWindow.setSize(1024, 768); // Resize the window to click the element - await this.app.client. - loadSettingsPage(). - click('#inputShowTrayIcon'). - waitForAppOptionsAutoSaved(); - - let config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); - config0.showTrayIcon.should.true; - - await this.app.client. - click('#inputShowTrayIcon'). - waitForAppOptionsAutoSaved(); - - config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); - config0.showTrayIcon.should.false; - }); - }); - - describe('Save tray icon theme on linux', () => { - env.shouldTest(it, process.platform === 'linux')('should be saved when it\'s selected', async () => { - env.addClientCommands(this.app.client); - await this.app.browserWindow.setSize(1024, 768); // Resize the window to click the element - await this.app.client. - loadSettingsPage(). - click('#inputShowTrayIcon'). - click('input[value="dark"]'). - pause(700); // wait auto-save - - const config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); - config0.trayIconTheme.should.equal('dark'); - - await this.app.client. - click('input[value="light"]'). - pause(700); // wait auto-save - - const config1 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); - config1.trayIconTheme.should.equal('light'); - }); - }); - }); - - describe('Leave app running in notification area when application window is closed', () => { - it('should appear on linux', async () => { - const expected = (process.platform === 'linux'); - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputMinimizeToTray'); - existing.should.equal(expected); - }); - }); - - describe.skip('Toggle window visibility when clicking on the tray icon', () => { - it('should appear on win32', async () => { - const expected = (process.platform === 'win32'); - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputToggleWindowOnTrayIconClick'); - existing.should.equal(expected); - }); - }); - - describe('Flash app window and taskbar icon when a new message is received', () => { - it('should appear on win32 and linux', async () => { - const expected = (process.platform === 'win32' || process.platform === 'linux'); - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputflashWindow'); - existing.should.equal(expected); - }); - }); - - describe('Show red badge on taskbar icon to indicate unread messages', () => { - it('should appear on darwin or win32', async () => { - const expected = (process.platform === 'darwin' || process.platform === 'win32'); - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputShowUnreadBadge'); - existing.should.equal(expected); - }); - }); - - describe('Check spelling', () => { - it('should appear and be selectable', async () => { - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting('#inputSpellChecker'); - existing.should.equal(true); - - const selected = await this.app.client.isSelected('#inputSpellChecker'); - selected.should.equal(true); - - const windowBounds = await this.app.browserWindow.getBounds(); - const inputLocation = await this.app.client.getLocation('#inputSpellChecker'); - const offset = (inputLocation.y - windowBounds.height) + 100; - - await this.app.client. - scroll(0, offset). - click('#inputSpellChecker'). - pause(5000); - const config1 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); - config1.useSpellChecker.should.equal(false); - }); - }); - - describe('Enable GPU hardware acceleration', () => { - it('should save selected option', async () => { - const ID_INPUT_ENABLE_HARDWARE_ACCELERATION = '#inputEnableHardwareAcceleration'; - env.addClientCommands(this.app.client); - await this.app.client. - loadSettingsPage(). - waitForExist(ID_INPUT_ENABLE_HARDWARE_ACCELERATION, 5000). - scroll(ID_INPUT_ENABLE_HARDWARE_ACCELERATION); - const selected = await this.app.client.isSelected(ID_INPUT_ENABLE_HARDWARE_ACCELERATION); - selected.should.equal(true); // default is true - - await this.app.client.click(ID_INPUT_ENABLE_HARDWARE_ACCELERATION). - waitForVisible('#appOptionsSaveIndicator', 5000). - waitForVisible('#appOptionsSaveIndicator', 5000, true); // at least 2500 ms to disappear - const config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); - config0.enableHardwareAcceleration.should.equal(false); - - await this.app.client.click(ID_INPUT_ENABLE_HARDWARE_ACCELERATION). - waitForVisible('#appOptionsSaveIndicator', 5000). - waitForVisible('#appOptionsSaveIndicator', 5000, true); // at least 2500 ms to disappear - const config1 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); - config1.enableHardwareAcceleration.should.equal(true); - }); - }); - }); - - describe('RemoveServerModal', () => { - const modalTitleSelector = '.modal-title=Remove Server'; - - beforeEach(async () => { - env.addClientCommands(this.app.client); - await this.app.client.loadSettingsPage(); - const existing = await this.app.client.isExisting(modalTitleSelector); - existing.should.be.false; - - const visible = await this.app.client.isVisible(modalTitleSelector); - visible.should.be.false; - - await this.app.client. - click('=Remove'). - waitForVisible(modalTitleSelector); - }); - - it('should remove existing team on click Remove', async () => { - await this.app.client. - element('.modal-dialog').click('.btn=Remove'). - waitForExist(modalTitleSelector, 5000, true); - - await this.app.client.waitForVisible('#serversSaveIndicator', 10000, true); - - const expectedConfig = JSON.parse(JSON.stringify(config.teams.slice(1))); - expectedConfig.forEach((value) => { - value.order--; - }); - - const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); - savedConfig.teams.should.deep.equal(expectedConfig); - }); - - it('should NOT remove existing team on click Cancel', async () => { - await this.app.client. - element('.modal-dialog').click('.btn=Cancel'). - waitForExist(modalTitleSelector, 5000, true); - - await this.app.client.waitForVisible('#serversSaveIndicator', 10000, true); - - const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); - savedConfig.teams.should.deep.equal(config.teams); - }); - - it('should disappear on click Close', async () => { - await this.app.client. - element('.modal-dialog').click('button.close'). - waitForVisible(modalTitleSelector, 10000, true); - const existing = await this.app.client.isExisting(modalTitleSelector); - existing.should.be.false; - }); - - it('should disappear on click background', async () => { - await this.app.browserWindow.setSize(1024, 768); // Resize the window to click the center of - await this.app.client. - click('body'). - waitForVisible(modalTitleSelector, 10000, true); - const existing = await this.app.client.isExisting(modalTitleSelector); - existing.should.be.false; - }); - }); - - describe('NewTeamModal', () => { - beforeEach(() => { - env.addClientCommands(this.app.client); - return this.app.client. - loadSettingsPage(). - click('#addNewServer'). - pause(1000); - }); - - it('should open the new server modal', () => { - return this.app.client.isExisting('#newServerModal').then((existing) => { - existing.should.be.true; - }); - }); - - it('should close the window after clicking cancel', () => { - return this.app.client. - click('#cancelNewServerModal'). - waitForExist('#newServerModal', 10000, true). - isExisting('#newServerModal').then((existing) => { - existing.should.be.false; - }); - }); - - it('should not be valid if no team name has been set', () => { - return this.app.client. - click('#saveNewServerModal'). - waitForExist('.has-error #teamNameInput', 10000). - isExisting('.has-error #teamNameInput').then((existing) => { - existing.should.be.true; - }); - }); - - it('should not be valid if no server address has been set', () => { - return this.app.client. - click('#saveNewServerModal'). - waitForExist('.has-error #teamUrlInput', 10000). - isExisting('.has-error #teamUrlInput').then((existing) => { - existing.should.be.true; - }); - }); - - describe('Valid server name', () => { - beforeEach(() => { - return this.app.client. - setValue('#teamNameInput', 'TestTeam'). - click('#saveNewServerModal'); - }); - - it('should not be marked invalid', () => { - return this.app.client. - isExisting('.has-error #teamNameInput').then((existing) => { - existing.should.be.false; - }); - }); - - it('should not be possible to click save', () => { - return this.app.client. - getAttribute('#saveNewServerModal', 'disabled').then((disabled) => { - disabled.should.equal('true'); - }); - }); - }); - - describe('Valid server url', () => { - beforeEach(() => { - return this.app.client. - setValue('#teamUrlInput', 'http://example.org'). - click('#saveNewServerModal'); - }); - - it('should be valid', () => { - return this.app.client. - isExisting('.has-error #teamUrlInput').then((existing) => { - existing.should.be.false; - }); - }); - - it('should not be possible to click save', () => { - return this.app.client. - getAttribute('#saveNewServerModal', 'disabled').then((disabled) => { - disabled.should.equal('true'); - }); - }); - }); - - it('should not be valid if an invalid server address has been set', () => { - return this.app.client. - setValue('#teamUrlInput', 'superInvalid url'). - click('#saveNewServerModal'). - pause(500). - isExisting('.has-error #teamUrlInput').then((existing) => { - existing.should.be.true; - }); - }); - - describe('Valid Team Settings', () => { - beforeEach(() => { - return this.app.client. - setValue('#teamUrlInput', 'http://example.org'). - setValue('#teamNameInput', 'TestTeam'); - }); - - it('should be possible to click add', () => { - return this.app.client. - getAttribute('#saveNewServerModal', 'disabled').then((disabled) => { - (disabled === null).should.be.true; - }); - }); - - it('should add the team to the config file', async () => { - await this.app.client. - click('#saveNewServerModal'). - waitForVisible('#newServerModal', 10000, true). - waitForVisible('#serversSaveIndicator', 10000). - waitForVisible('#serversSaveIndicator', 10000, true). // at least 2500 ms to disappear - waitUntilWindowLoaded(); - - const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); - savedConfig.teams.should.deep.contain({ - name: 'TestTeam', - url: 'http://example.org', - order: 2, - }); - }); - }); - }); -}); + +// TODO: fix test with new settings window + +// 'use strict'; + +// const fs = require('fs'); + +// const env = require('../../modules/environment'); +// const {asyncSleep} = require('../../modules/utils'); + +// describe('renderer/settings.html', function desc() { +// this.timeout(30000); + +// const config = { +// version: 2, +// teams: [{ +// name: 'example', +// url: env.mattermostURL, +// order: 0, +// }, { +// name: 'github', +// url: 'https://github.com/', +// order: 1, +// }], +// showTrayIcon: false, +// trayIconTheme: 'light', +// minimizeToTray: false, +// notifications: { +// flashWindow: 0, +// bounceIcon: false, +// bounceIconType: 'informational', +// }, +// showUnreadBadge: true, +// useSpellChecker: true, +// enableHardwareAcceleration: true, +// autostart: true, +// darkMode: false, +// }; + +// beforeEach(async () => { +// fs.writeFileSync(env.configFilePath, JSON.stringify(config)); +// await asyncSleep(1000); +// this.app = env.getSpectronApp(); +// await this.app.start(); +// }); + +// afterEach(async () => { +// if (this.app && this.app.isRunning()) { +// await this.app.stop(); +// } +// }); + +// describe('Close button', async () => { +// // it.skip('should show index.html when it\'s clicked', async () => { +// // env.addClientCommands(this.app.client); +// // await this.app.client. +// // loadSettingsPage(). +// // click('#btnClose'). +// // pause(1000); +// // const url = await this.app.client.getUrl(); +// // url.should.match(/\/index.html(\?.+)?$/); +// // }); + +// it('should be disabled when the number of servers is zero', async () => { +// await this.app.stop(); +// env.cleanTestConfig(); +// await this.app.start(); + +// await this.app.client.waitUntilWindowLoaded(). +// waitForVisible('#newServerModal', 10000). +// click('#cancelNewServerModal'); +// let isCloseButtonEnabled = await this.app.client.isEnabled('#btnClose'); +// isCloseButtonEnabled.should.equal(false); + +// await this.app.client. +// waitForVisible('#newServerModal', true). +// pause(250). +// click('#addNewServer'). +// waitForVisible('#newServerModal'). +// setValue('#teamNameInput', 'TestTeam'). +// pause(100). +// setValue('#teamUrlInput', 'http://example.org'). +// click('#saveNewServerModal'). +// waitForVisible('#newServerModal', true). +// waitForVisible('#serversSaveIndicator'). +// waitForVisible('#serversSaveIndicator', 10000, true); // at least 2500 ms to disappear +// isCloseButtonEnabled = await this.app.client.isEnabled('#btnClose'); +// isCloseButtonEnabled.should.equal(true); +// }); +// }); + +// it('should show NewServerModal after all servers are removed', async () => { +// const modalTitleSelector = '.modal-title=Remove Server'; +// env.addClientCommands(this.app.client); +// await this.app.client. +// loadSettingsPage(). +// click('=Remove'). +// waitForVisible(modalTitleSelector). +// element('.modal-dialog').click('.btn=Remove'). +// pause(500). +// click('=Remove'). +// waitForVisible(modalTitleSelector). +// element('.modal-dialog').click('.btn=Remove'). +// pause(500); +// const isModalExisting = await this.app.client.isExisting('#newServerModal'); +// isModalExisting.should.be.true; +// }); + +// // describe('Server list', () => { +// // it.skip('should open the corresponding tab when a server list item is clicked', async () => { +// // env.addClientCommands(this.app.client); +// // await this.app.client. +// // loadSettingsPage(). +// // click('h4=example'). +// // pause(1000). +// // waitUntilWindowLoaded(); +// // let indexURL = await this.app.client.getUrl(); +// // indexURL.should.match(/\/index.html(\?.+)?$/); + +// // let isView0Visible = await this.app.client.isVisible('#mattermostView0'); +// // isView0Visible.should.be.true; + +// // let isView1Visible = await this.app.client.isVisible('#mattermostView1'); +// // isView1Visible.should.be.false; + +// // await this.app.client. +// // loadSettingsPage(). +// // click('h4=github'). +// // pause(1000). +// // waitUntilWindowLoaded(); +// // indexURL = await this.app.client.getUrl(); +// // indexURL.should.match(/\/index.html(\?.+)?$/); + +// // isView0Visible = await this.app.client.isVisible('#mattermostView0'); +// // isView0Visible.should.be.false; + +// // isView1Visible = await this.app.client.isVisible('#mattermostView1'); +// // isView1Visible.should.be.true; +// // }); +// // }); + +// describe('Options', () => { +// // describe.skip('Hide Menu Bar', () => { +// // it('should appear on win32 or linux', async () => { +// // const expected = (process.platform === 'win32' || process.platform === 'linux'); +// // env.addClientCommands(this.app.client); +// // await this.app.client.loadSettingsPage(); +// // const existing = await this.app.client.isExisting('#inputHideMenuBar'); +// // existing.should.equal(expected); +// // }); + +// // [true, false].forEach((v) => { +// // env.shouldTest(it, env.isOneOf(['win32', 'linux']))(`should be saved and loaded: ${v}`, async () => { +// // env.addClientCommands(this.app.client); +// // await this.app.client. +// // loadSettingsPage(). +// // scroll('#inputHideMenuBar'); +// // const isSelected = await this.app.client.isSelected('#inputHideMenuBar'); +// // if (isSelected !== v) { +// // await this.app.client.click('#inputHideMenuBar'); +// // } + +// // await this.app.client. +// // pause(600). +// // click('#btnClose'). +// // pause(1000); + +// // const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); +// // savedConfig.hideMenuBar.should.equal(v); + +// // let autoHide = await this.app.browserWindow.isMenuBarAutoHide(); +// // autoHide.should.equal(v); + +// // // confirm actual behavior +// // await this.app.restart(); +// // env.addClientCommands(this.app.client); + +// // autoHide = await this.app.browserWindow.isMenuBarAutoHide(); +// // autoHide.should.equal(v); + +// // await this.app.loadSettingsPage(); +// // autoHide = await this.app.client.isSelected('#inputHideMenuBar'); +// // autoHide.should.equal(v); +// // }); +// // }); +// // }); + +// describe('Start app on login', () => { +// it('should appear on win32 or linux', async () => { +// const expected = (process.platform === 'win32' || process.platform === 'linux'); +// env.addClientCommands(this.app.client); +// await this.app.client.loadSettingsPage(); +// const existing = await this.app.client.isExisting('#inputAutoStart'); +// existing.should.equal(expected); +// }); +// }); + +// describe('Show icon in menu bar / notification area', () => { +// it('should appear on darwin or linux', async () => { +// const expected = (process.platform === 'darwin' || process.platform === 'linux'); +// env.addClientCommands(this.app.client); +// await this.app.client.loadSettingsPage(); +// const existing = await this.app.client.isExisting('#inputShowTrayIcon'); +// existing.should.equal(expected); +// }); + +// describe('Save tray icon setting on mac', () => { +// env.shouldTest(it, env.isOneOf(['darwin', 'linux']))('should be saved when it\'s selected', async () => { +// env.addClientCommands(this.app.client); +// await this.app.browserWindow.setSize(1024, 768); // Resize the window to click the element +// await this.app.client. +// loadSettingsPage(). +// click('#inputShowTrayIcon'). +// waitForAppOptionsAutoSaved(); + +// let config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); +// config0.showTrayIcon.should.true; + +// await this.app.client. +// click('#inputShowTrayIcon'). +// waitForAppOptionsAutoSaved(); + +// config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); +// config0.showTrayIcon.should.false; +// }); +// }); + +// describe('Save tray icon theme on linux', () => { +// env.shouldTest(it, process.platform === 'linux')('should be saved when it\'s selected', async () => { +// env.addClientCommands(this.app.client); +// await this.app.browserWindow.setSize(1024, 768); // Resize the window to click the element +// await this.app.client. +// loadSettingsPage(). +// click('#inputShowTrayIcon'). +// click('input[value="dark"]'). +// pause(700); // wait auto-save + +// const config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); +// config0.trayIconTheme.should.equal('dark'); + +// await this.app.client. +// click('input[value="light"]'). +// pause(700); // wait auto-save + +// const config1 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); +// config1.trayIconTheme.should.equal('light'); +// }); +// }); +// }); + +// describe('Leave app running in notification area when application window is closed', () => { +// it('should appear on linux', async () => { +// const expected = (process.platform === 'linux'); +// env.addClientCommands(this.app.client); +// await this.app.client.loadSettingsPage(); +// const existing = await this.app.client.isExisting('#inputMinimizeToTray'); +// existing.should.equal(expected); +// }); +// }); + +// // describe.skip('Toggle window visibility when clicking on the tray icon', () => { +// // it('should appear on win32', async () => { +// // const expected = (process.platform === 'win32'); +// // env.addClientCommands(this.app.client); +// // await this.app.client.loadSettingsPage(); +// // const existing = await this.app.client.isExisting('#inputToggleWindowOnTrayIconClick'); +// // existing.should.equal(expected); +// // }); +// // }); + +// describe('Flash app window and taskbar icon when a new message is received', () => { +// it('should appear on win32 and linux', async () => { +// const expected = (process.platform === 'win32' || process.platform === 'linux'); +// env.addClientCommands(this.app.client); +// await this.app.client.loadSettingsPage(); +// const existing = await this.app.client.isExisting('#inputflashWindow'); +// existing.should.equal(expected); +// }); +// }); + +// describe('Show red badge on taskbar icon to indicate unread messages', () => { +// it('should appear on darwin or win32', async () => { +// const expected = (process.platform === 'darwin' || process.platform === 'win32'); +// env.addClientCommands(this.app.client); +// await this.app.client.loadSettingsPage(); +// const existing = await this.app.client.isExisting('#inputShowUnreadBadge'); +// existing.should.equal(expected); +// }); +// }); + +// describe('Check spelling', () => { +// it('should appear and be selectable', async () => { +// env.addClientCommands(this.app.client); +// await this.app.client.loadSettingsPage(); +// const existing = await this.app.client.isExisting('#inputSpellChecker'); +// existing.should.equal(true); + +// const selected = await this.app.client.isSelected('#inputSpellChecker'); +// selected.should.equal(true); + +// const windowBounds = await this.app.browserWindow.getBounds(); +// const inputLocation = await this.app.client.getLocation('#inputSpellChecker'); +// const offset = (inputLocation.y - windowBounds.height) + 100; + +// await this.app.client. +// scroll(0, offset). +// click('#inputSpellChecker'). +// pause(5000); +// const config1 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); +// config1.useSpellChecker.should.equal(false); +// }); +// }); + +// describe('Enable GPU hardware acceleration', () => { +// it('should save selected option', async () => { +// const ID_INPUT_ENABLE_HARDWARE_ACCELERATION = '#inputEnableHardwareAcceleration'; +// env.addClientCommands(this.app.client); +// await this.app.client. +// loadSettingsPage(). +// waitForExist(ID_INPUT_ENABLE_HARDWARE_ACCELERATION, 5000). +// scroll(ID_INPUT_ENABLE_HARDWARE_ACCELERATION); +// const selected = await this.app.client.isSelected(ID_INPUT_ENABLE_HARDWARE_ACCELERATION); +// selected.should.equal(true); // default is true + +// await this.app.client.click(ID_INPUT_ENABLE_HARDWARE_ACCELERATION). +// waitForVisible('#appOptionsSaveIndicator', 5000). +// waitForVisible('#appOptionsSaveIndicator', 5000, true); // at least 2500 ms to disappear +// const config0 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); +// config0.enableHardwareAcceleration.should.equal(false); + +// await this.app.client.click(ID_INPUT_ENABLE_HARDWARE_ACCELERATION). +// waitForVisible('#appOptionsSaveIndicator', 5000). +// waitForVisible('#appOptionsSaveIndicator', 5000, true); // at least 2500 ms to disappear +// const config1 = JSON.parse(fs.readFileSync(env.configFilePath, 'utf-8')); +// config1.enableHardwareAcceleration.should.equal(true); +// }); +// }); +// }); + +// describe('RemoveServerModal', () => { +// const modalTitleSelector = '.modal-title=Remove Server'; + +// beforeEach(async () => { +// env.addClientCommands(this.app.client); +// await this.app.client.loadSettingsPage(); +// const existing = await this.app.client.isExisting(modalTitleSelector); +// existing.should.be.false; + +// const visible = await this.app.client.isVisible(modalTitleSelector); +// visible.should.be.false; + +// await this.app.client. +// click('=Remove'). +// waitForVisible(modalTitleSelector); +// }); + +// it('should remove existing team on click Remove', async () => { +// await this.app.client. +// element('.modal-dialog').click('.btn=Remove'). +// waitForExist(modalTitleSelector, 5000, true); + +// await this.app.client.waitForVisible('#serversSaveIndicator', 10000, true); + +// const expectedConfig = JSON.parse(JSON.stringify(config.teams.slice(1))); +// expectedConfig.forEach((value) => { +// value.order--; +// }); + +// const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); +// savedConfig.teams.should.deep.equal(expectedConfig); +// }); + +// it('should NOT remove existing team on click Cancel', async () => { +// await this.app.client. +// element('.modal-dialog').click('.btn=Cancel'). +// waitForExist(modalTitleSelector, 5000, true); + +// await this.app.client.waitForVisible('#serversSaveIndicator', 10000, true); + +// const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); +// savedConfig.teams.should.deep.equal(config.teams); +// }); + +// it('should disappear on click Close', async () => { +// await this.app.client. +// element('.modal-dialog').click('button.close'). +// waitForVisible(modalTitleSelector, 10000, true); +// const existing = await this.app.client.isExisting(modalTitleSelector); +// existing.should.be.false; +// }); + +// it('should disappear on click background', async () => { +// await this.app.browserWindow.setSize(1024, 768); // Resize the window to click the center of +// await this.app.client. +// click('body'). +// waitForVisible(modalTitleSelector, 10000, true); +// const existing = await this.app.client.isExisting(modalTitleSelector); +// existing.should.be.false; +// }); +// }); + +// describe('NewTeamModal', () => { +// beforeEach(() => { +// env.addClientCommands(this.app.client); +// return this.app.client. +// loadSettingsPage(). +// click('#addNewServer'). +// pause(1000); +// }); + +// it('should open the new server modal', () => { +// return this.app.client.isExisting('#newServerModal').then((existing) => { +// existing.should.be.true; +// }); +// }); + +// it('should close the window after clicking cancel', () => { +// return this.app.client. +// click('#cancelNewServerModal'). +// waitForExist('#newServerModal', 10000, true). +// isExisting('#newServerModal').then((existing) => { +// existing.should.be.false; +// }); +// }); + +// it('should not be valid if no team name has been set', () => { +// return this.app.client. +// click('#saveNewServerModal'). +// waitForExist('.has-error #teamNameInput', 10000). +// isExisting('.has-error #teamNameInput').then((existing) => { +// existing.should.be.true; +// }); +// }); + +// it('should not be valid if no server address has been set', () => { +// return this.app.client. +// click('#saveNewServerModal'). +// waitForExist('.has-error #teamUrlInput', 10000). +// isExisting('.has-error #teamUrlInput').then((existing) => { +// existing.should.be.true; +// }); +// }); + +// describe('Valid server name', () => { +// beforeEach(() => { +// return this.app.client. +// setValue('#teamNameInput', 'TestTeam'). +// click('#saveNewServerModal'); +// }); + +// it('should not be marked invalid', () => { +// return this.app.client. +// isExisting('.has-error #teamNameInput').then((existing) => { +// existing.should.be.false; +// }); +// }); + +// it('should not be possible to click save', () => { +// return this.app.client. +// getAttribute('#saveNewServerModal', 'disabled').then((disabled) => { +// disabled.should.equal('true'); +// }); +// }); +// }); + +// describe('Valid server url', () => { +// beforeEach(() => { +// return this.app.client. +// setValue('#teamUrlInput', 'http://example.org'). +// click('#saveNewServerModal'); +// }); + +// it('should be valid', () => { +// return this.app.client. +// isExisting('.has-error #teamUrlInput').then((existing) => { +// existing.should.be.false; +// }); +// }); + +// it('should not be possible to click save', () => { +// return this.app.client. +// getAttribute('#saveNewServerModal', 'disabled').then((disabled) => { +// disabled.should.equal('true'); +// }); +// }); +// }); + +// it('should not be valid if an invalid server address has been set', () => { +// return this.app.client. +// setValue('#teamUrlInput', 'superInvalid url'). +// click('#saveNewServerModal'). +// pause(500). +// isExisting('.has-error #teamUrlInput').then((existing) => { +// existing.should.be.true; +// }); +// }); + +// describe('Valid Team Settings', () => { +// beforeEach(() => { +// return this.app.client. +// setValue('#teamUrlInput', 'http://example.org'). +// setValue('#teamNameInput', 'TestTeam'); +// }); + +// it('should be possible to click add', () => { +// return this.app.client. +// getAttribute('#saveNewServerModal', 'disabled').then((disabled) => { +// (disabled === null).should.be.true; +// }); +// }); + +// it('should add the team to the config file', async () => { +// await this.app.client. +// click('#saveNewServerModal'). +// waitForVisible('#newServerModal', 10000, true). +// waitForVisible('#serversSaveIndicator', 10000). +// waitForVisible('#serversSaveIndicator', 10000, true). // at least 2500 ms to disappear +// waitUntilWindowLoaded(); + +// const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); +// savedConfig.teams.should.deep.contain({ +// name: 'TestTeam', +// url: 'http://example.org', +// order: 2, +// }); +// }); +// }); +// }); +// }); diff --git a/test/specs/main/trusted_origins_test.js b/test/specs/main/trusted_origins_test.js deleted file mode 100644 index 5d4c579c..00000000 --- a/test/specs/main/trusted_origins_test.js +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -'use strict'; - -import assert from 'assert'; -import 'airbnb-js-shims/target/es2015'; - -import TrustedOriginsStore from '../../../src/main/trustedOrigins.js'; -import {BASIC_AUTH_PERMISSION} from '../../../src/common/permissions.js'; - -function mockTOS(fileName, returnvalue) { - const tos = new TrustedOriginsStore(fileName); - tos.readFromFile = () => { - return returnvalue; - }; - return tos; -} - -describe('Trusted Origins', () => { - describe('validate load', () => { - it('should be empty if there is no file', () => { - const tos = mockTOS('emptyfile', null); - tos.load(); - assert.deepEqual(tos.data.size, 0); - }); - - it('should throw an error if data isn\'t an object', () => { - const tos = mockTOS('notobject', 'this is not my object!'); - - assert.throws(tos.load, SyntaxError); - }); - - it('should throw an error if data isn\'t in the expected format', () => { - const tos = mockTOS('badobject', '{"https://mattermost.com": "this is not my object!"}'); - assert.throws(tos.load, /^Error: Provided TrustedOrigins file does not validate, using defaults instead\.$/); - }); - - it('should drop keys that aren\'t urls', () => { - const tos = mockTOS('badobject2', `{"this is not an uri": {"${BASIC_AUTH_PERMISSION}": true}}`); - tos.load(); - assert.equal(typeof tos.data['this is not an uri'], 'undefined'); - }); - - it('should contain valid data if everything goes right', () => { - const value = { - 'https://mattermost.com': { - [BASIC_AUTH_PERMISSION]: true, - }}; - const tos = mockTOS('okfile', JSON.stringify(value)); - tos.load(); - assert.deepEqual(Object.fromEntries(tos.data.entries()), value); - }); - }); - describe('validate testing permissions', () => { - const value = { - 'https://mattermost.com': { - [BASIC_AUTH_PERMISSION]: true, - }, - 'https://notmattermost.com': { - [BASIC_AUTH_PERMISSION]: false, - }, - }; - const tos = mockTOS('permission_test', JSON.stringify(value)); - tos.load(); - it('tos should contain 2 elements', () => { - assert.equal(tos.data.size, 2); - }); - it('should say ok if the permission is set', () => { - assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), true); - }); - it('should say ko if the permission is set to false', () => { - assert.equal(tos.checkPermission('https://notmattermost.com', BASIC_AUTH_PERMISSION), false); - }); - it('should say ko if the uri is not set', () => { - assert.equal(tos.checkPermission('https://undefined.com', BASIC_AUTH_PERMISSION), null); - }); - it('should say null if the permission is unknown', () => { - assert.equal(tos.checkPermission('https://mattermost.com'), null); - }); - }); - - describe('validate deleting permissions', () => { - const value = { - 'https://mattermost.com': { - [BASIC_AUTH_PERMISSION]: true, - }, - 'https://notmattermost.com': { - [BASIC_AUTH_PERMISSION]: false, - }, - }; - const tos = mockTOS('permission_test', JSON.stringify(value)); - tos.load(); - it('deleting revokes access', () => { - assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), true); - tos.delete('https://mattermost.com'); - assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), null); - }); - }); -}); \ No newline at end of file diff --git a/test/specs/main/user_activity_monitor_test.js b/test/specs/main/user_activity_monitor_test.js index 51beb662..38d2c5d3 100644 --- a/test/specs/main/user_activity_monitor_test.js +++ b/test/specs/main/user_activity_monitor_test.js @@ -5,65 +5,65 @@ import assert from 'assert'; import UserActivityMonitor from '../../../src/main/UserActivityMonitor'; describe('UserActivityMonitor', () => { - describe('updateIdleTime', () => { - it('should set idle time to provided value', () => { - const userActivityMonitor = new UserActivityMonitor(); - const idleTime = Math.round(Date.now() / 1000); - userActivityMonitor.updateIdleTime(idleTime); - assert.equal(userActivityMonitor.userIdleTime, idleTime); - }); - }); - - describe('updateUserActivityStatus', () => { - let userActivityMonitor; - - beforeEach(() => { - userActivityMonitor = new UserActivityMonitor(); + describe('updateIdleTime', () => { + it('should set idle time to provided value', () => { + const userActivityMonitor = new UserActivityMonitor(); + const idleTime = Math.round(Date.now() / 1000); + userActivityMonitor.updateIdleTime(idleTime); + assert.equal(userActivityMonitor.userIdleTime, idleTime); + }); }); - it('should set user status to active', () => { - userActivityMonitor.setActivityState(true); - assert.equal(userActivityMonitor.userIsActive, true); - }); - it('should set user status to inactive', () => { - userActivityMonitor.setActivityState(false); - assert.equal(userActivityMonitor.userIsActive, false); - }); - }); + describe('updateUserActivityStatus', () => { + let userActivityMonitor; - describe('sendStatusUpdate', () => { - let userActivityMonitor; + beforeEach(() => { + userActivityMonitor = new UserActivityMonitor(); + }); - beforeEach(() => { - userActivityMonitor = new UserActivityMonitor(); + it('should set user status to active', () => { + userActivityMonitor.setActivityState(true); + assert.equal(userActivityMonitor.userIsActive, true); + }); + it('should set user status to inactive', () => { + userActivityMonitor.setActivityState(false); + assert.equal(userActivityMonitor.userIsActive, false); + }); }); - it('should emit a non-system triggered status event indicating a user is active', () => { - userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { - assert.equal(userIsActive && !isSystemEvent, true); - }); - userActivityMonitor.setActivityState(true, false); - }); + describe('sendStatusUpdate', () => { + let userActivityMonitor; - it('should emit a non-system triggered status event indicating a user is inactive', () => { - userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { - assert.equal(!userIsActive && !isSystemEvent, true); - }); - userActivityMonitor.setActivityState(false, false); - }); + beforeEach(() => { + userActivityMonitor = new UserActivityMonitor(); + }); - it('should emit a system triggered status event indicating a user is active', () => { - userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { - assert.equal(userIsActive && isSystemEvent, true); - }); - userActivityMonitor.setActivityState(true, true); - }); + it('should emit a non-system triggered status event indicating a user is active', () => { + userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { + assert.equal(userIsActive && !isSystemEvent, true); + }); + userActivityMonitor.setActivityState(true, false); + }); - it('should emit a system triggered status event indicating a user is inactive', () => { - userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { - assert.equal(!userIsActive && isSystemEvent, true); - }); - userActivityMonitor.setActivityState(false, true); + it('should emit a non-system triggered status event indicating a user is inactive', () => { + userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { + assert.equal(!userIsActive && !isSystemEvent, true); + }); + userActivityMonitor.setActivityState(false, false); + }); + + it('should emit a system triggered status event indicating a user is active', () => { + userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { + assert.equal(userIsActive && isSystemEvent, true); + }); + userActivityMonitor.setActivityState(true, true); + }); + + it('should emit a system triggered status event indicating a user is inactive', () => { + userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => { + assert.equal(!userIsActive && isSystemEvent, true); + }); + userActivityMonitor.setActivityState(false, true); + }); }); - }); }); diff --git a/test/specs/security_test.js b/test/specs/security_test.js index 02f5169e..fcb1d456 100644 --- a/test/specs/security_test.js +++ b/test/specs/security_test.js @@ -3,123 +3,124 @@ // See LICENSE.txt for license information. 'use strict'; -const path = require('path'); -const fs = require('fs'); -const http = require('http'); +// const fs = require('fs'); -const env = require('../modules/environment'); +// const path = require('path'); +// const http = require('http'); -describe.skip('security', function desc() { - this.timeout(30000); +// const env = require('../modules/environment'); - const serverPort = 8181; - const testURL = `http://localhost:${serverPort}`; +// describe.skip('security', function desc() { +// this.timeout(30000); - const config = { - version: 2, - teams: [{ - name: 'example_1', - url: testURL, - order: 0, - }, { - name: 'example_2', - url: testURL, - order: 1, - }], - }; +// const serverPort = 8181; +// const testURL = `http://localhost:${serverPort}`; - before(() => { - this.server = http.createServer((req, res) => { - res.writeHead(200, { - 'Content-Type': 'text/html', - }); - res.end(fs.readFileSync(path.resolve(env.sourceRootDir, 'test/modules/test.html'), 'utf-8')); - }).listen(serverPort, '127.0.0.1'); - }); +// const config = { +// version: 2, +// teams: [{ +// name: 'example_1', +// url: testURL, +// order: 0, +// }, { +// name: 'example_2', +// url: testURL, +// order: 1, +// }], +// }; - beforeEach(() => { - fs.writeFileSync(env.configFilePath, JSON.stringify(config)); - this.app = env.getSpectronApp(); - return this.app.start(); - }); +// before(() => { +// this.server = http.createServer((req, res) => { +// res.writeHead(200, { +// 'Content-Type': 'text/html', +// }); +// res.end(fs.readFileSync(path.resolve(env.sourceRootDir, 'test/modules/test.html'), 'utf-8')); +// }).listen(serverPort, '127.0.0.1'); +// }); - afterEach(() => { - if (this.app && this.app.isRunning()) { - return this.app.stop(); - } - return true; - }); +// beforeEach(() => { +// fs.writeFileSync(env.configFilePath, JSON.stringify(config)); +// this.app = env.getSpectronApp(); +// return this.app.start(); +// }); - after((done) => { - this.server.close(done); - }); +// afterEach(() => { +// if (this.app && this.app.isRunning()) { +// return this.app.stop(); +// } +// return true; +// }); - it('should NOT be able to call Node.js API in webview', () => { - env.addClientCommands(this.app.client); +// after((done) => { +// this.server.close(done); +// }); - // webview is handled as a window by chromedriver. - return this.app.client. - windowByIndex(1).isNodeEnabled().then((enabled) => { - enabled.should.be.false; - }). - windowByIndex(2).isNodeEnabled().then((enabled) => { - enabled.should.be.false; - }). - windowByIndex(0). - getAttribute('webview', 'nodeintegration').then((nodeintegration) => { - // nodeintegration is an array of string - nodeintegration.forEach((n) => { - n.should.equal('false'); - }); - }); - }); +// it('should NOT be able to call Node.js API in webview', () => { +// env.addClientCommands(this.app.client); - it('should NOT be able to call Node.js API in a new window', () => { - env.addClientCommands(this.app.client); - const client = this.app.client; - return this.app.client. - windowByIndex(1). // in the first webview - execute(() => { - open_window(); - }). - waitUntil(() => { - return client.windowHandles().then((handles) => { - return handles.value.length === 4; - }); - }, 5000, 'expected a new window'). - windowByIndex(3).isNodeEnabled().then((enabled) => { - enabled.should.be.false; - }); - }); +// // webview is handled as a window by chromedriver. +// return this.app.client. +// windowByIndex(1).isNodeEnabled().then((enabled) => { +// enabled.should.be.false; +// }). +// windowByIndex(2).isNodeEnabled().then((enabled) => { +// enabled.should.be.false; +// }). +// windowByIndex(0). +// getAttribute('webview', 'nodeintegration').then((nodeintegration) => { +// // nodeintegration is an array of string +// nodeintegration.forEach((n) => { +// n.should.equal('false'); +// }); +// }); +// }); - it('should NOT be able to call eval() in any window', () => { - env.addClientCommands(this.app.client); - const tryEval = (index) => { - return this.app.client. - windowByIndex(index). - execute(() => { - return eval('1 + 1'); - }).then((result) => { - throw new Error(`Promise was unexpectedly fulfilled (result: ${result})`); - }, (error) => { - (error !== null).should.be.true; - }); - }; - const tryEvalInSettingsPage = () => { - return this.app.client. - windowByIndex(0). - loadSettingsPage(). - execute(() => { - return eval('1 + 1'); - }).then((result) => { - throw new Error(`Promise was unexpectedly fulfilled (result: ${result})`); - }, (error) => { - (error !== null).should.be.true; - }); - }; - return Promise.all([ - tryEval(0), - tryEvalInSettingsPage(), - ]); - }); -}); +// it('should NOT be able to call Node.js API in a new window', () => { +// env.addClientCommands(this.app.client); +// const client = this.app.client; +// return this.app.client. +// windowByIndex(1). // in the first webview +// execute(() => { +// open_window(); +// }). +// waitUntil(() => { +// return client.windowHandles().then((handles) => { +// return handles.value.length === 4; +// }); +// }, 5000, 'expected a new window'). +// windowByIndex(3).isNodeEnabled().then((enabled) => { +// enabled.should.be.false; +// }); +// }); + +// it('should NOT be able to call eval() in any window', () => { +// env.addClientCommands(this.app.client); +// const tryEval = (index) => { +// return this.app.client. +// windowByIndex(index). +// execute(() => { +// return eval('1 + 1'); +// }).then((result) => { +// throw new Error(`Promise was unexpectedly fulfilled (result: ${result})`); +// }, (error) => { +// (error !== null).should.be.true; +// }); +// }; +// const tryEvalInSettingsPage = () => { +// return this.app.client. +// windowByIndex(0). +// loadSettingsPage(). +// execute(() => { +// return eval('1 + 1'); +// }).then((result) => { +// throw new Error(`Promise was unexpectedly fulfilled (result: ${result})`); +// }, (error) => { +// (error !== null).should.be.true; +// }); +// }; +// return Promise.all([ +// tryEval(0), +// tryEvalInSettingsPage(), +// ]); +// }); +// }); diff --git a/test/specs/spellchecker_test.js b/test/specs/spellchecker_test.js index d22ff081..189e4f32 100644 --- a/test/specs/spellchecker_test.js +++ b/test/specs/spellchecker_test.js @@ -1,219 +1,152 @@ // Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import path from 'path'; +//import path from 'path'; -import SpellChecker from '../../src/main/SpellChecker'; +// TODO: reenable with the new spellchecker -describe('main/Spellchecker.js', function() { - describe('getSpellCheckerLocale()', () => { - it('should return recognized locale', () => { - SpellChecker.getSpellCheckerLocale('en').should.equal('en-US'); - SpellChecker.getSpellCheckerLocale('en-US').should.equal('en-US'); +// describe('main/Spellchecker.js', function() { +// describe('getSpellCheckerLocale()', () => { +// it('should return recognized locale', () => { +// // SpellChecker.getSpellCheckerLocale('en').should.equal('en-US'); +// // SpellChecker.getSpellCheckerLocale('en-US').should.equal('en-US'); - SpellChecker.getSpellCheckerLocale('fr').should.equal('fr-FR'); - SpellChecker.getSpellCheckerLocale('fr-FR').should.equal('fr-FR'); +// // SpellChecker.getSpellCheckerLocale('fr').should.equal('fr-FR'); +// // SpellChecker.getSpellCheckerLocale('fr-FR').should.equal('fr-FR'); - SpellChecker.getSpellCheckerLocale('de').should.equal('de-DE'); - SpellChecker.getSpellCheckerLocale('de-DE').should.equal('de-DE'); +// // SpellChecker.getSpellCheckerLocale('de').should.equal('de-DE'); +// // SpellChecker.getSpellCheckerLocale('de-DE').should.equal('de-DE'); - SpellChecker.getSpellCheckerLocale('es').should.equal('es-ES'); - SpellChecker.getSpellCheckerLocale('es-ES').should.equal('es-ES'); +// // SpellChecker.getSpellCheckerLocale('es').should.equal('es-ES'); +// // SpellChecker.getSpellCheckerLocale('es-ES').should.equal('es-ES'); - SpellChecker.getSpellCheckerLocale('nl').should.equal('nl-NL'); - SpellChecker.getSpellCheckerLocale('nl-NL').should.equal('nl-NL'); +// // SpellChecker.getSpellCheckerLocale('nl').should.equal('nl-NL'); +// // SpellChecker.getSpellCheckerLocale('nl-NL').should.equal('nl-NL'); - SpellChecker.getSpellCheckerLocale('pl').should.equal('pl-PL'); - SpellChecker.getSpellCheckerLocale('pl-PL').should.equal('pl-PL'); - SpellChecker.getSpellCheckerLocale('pt').should.equal('pt-BR'); - SpellChecker.getSpellCheckerLocale('pt-BR').should.equal('pt-BR'); +// // SpellChecker.getSpellCheckerLocale('pl').should.equal('pl-PL'); +// // SpellChecker.getSpellCheckerLocale('pl-PL').should.equal('pl-PL'); +// // SpellChecker.getSpellCheckerLocale('pt').should.equal('pt-BR'); +// // SpellChecker.getSpellCheckerLocale('pt-BR').should.equal('pt-BR'); - SpellChecker.getSpellCheckerLocale('ja').should.equal('en-US'); - SpellChecker.getSpellCheckerLocale('ja-JP').should.equal('en-US'); +// // SpellChecker.getSpellCheckerLocale('ja').should.equal('en-US'); +// // SpellChecker.getSpellCheckerLocale('ja-JP').should.equal('en-US'); - SpellChecker.getSpellCheckerLocale('it').should.equal('it-IT'); - SpellChecker.getSpellCheckerLocale('it-IT').should.equal('it-IT'); +// // SpellChecker.getSpellCheckerLocale('it').should.equal('it-IT'); +// // SpellChecker.getSpellCheckerLocale('it-IT').should.equal('it-IT'); +// }); +// }); - SpellChecker.getSpellCheckerLocale('ru').should.equal('ru-RU'); - SpellChecker.getSpellCheckerLocale('ru-RU').should.equal('ru-RU'); +// describe('en-US', function() { +// const spellchecker = null; - SpellChecker.getSpellCheckerLocale('sv').should.equal('sv-SE'); - SpellChecker.getSpellCheckerLocale('sv-SE').should.equal('sv-SE'); +// // before(function(done) { +// // // spellchecker = new SpellChecker( +// // // 'en-US', +// // // path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), +// // // done +// // // ); +// // }); - SpellChecker.getSpellCheckerLocale('uk').should.equal('uk-UA'); - SpellChecker.getSpellCheckerLocale('uk-UA').should.equal('uk-UA'); - }); - }); +// it('should spellcheck', function() { +// // https://github.com/jfmdev/simple-spellchecker/issues/3 +// spellchecker.spellCheck('spell').should.equal(true); +// spellchecker.spellCheck('spel').should.equal(false); +// spellchecker.spellCheck('December').should.equal(true); +// spellchecker.spellCheck('december').should.equal(true); +// spellchecker.spellCheck('English').should.equal(true); +// spellchecker.spellCheck('Japan').should.equal(true); +// }); - describe('en-US', function() { - let spellchecker = null; +// it('should allow contractions', function() { +// spellchecker.spellCheck("shouldn't").should.equal(true); +// spellchecker.spellCheck('shouldn').should.equal(true); +// }); - before(function(done) { - spellchecker = new SpellChecker( - 'en-US', - path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), - done - ); - }); +// it('should allow numerals', function() { +// spellchecker.spellCheck('1').should.equal(true); +// spellchecker.spellCheck('-100').should.equal(true); +// spellchecker.spellCheck('3.14').should.equal(true); +// }); - it('should spellcheck', function() { - // https://github.com/jfmdev/simple-spellchecker/issues/3 - spellchecker.spellCheck('spell').should.equal(true); - spellchecker.spellCheck('spel').should.equal(false); - spellchecker.spellCheck('December').should.equal(true); - spellchecker.spellCheck('december').should.equal(true); - spellchecker.spellCheck('English').should.equal(true); - spellchecker.spellCheck('Japan').should.equal(true); - }); +// it('should allow "Mattermost"', function() { +// spellchecker.spellCheck('Mattermost').should.equal(true); +// spellchecker.spellCheck('mattermost').should.equal(true); +// }); - it('should allow contractions', function() { - spellchecker.spellCheck("shouldn't").should.equal(true); - spellchecker.spellCheck('shouldn').should.equal(true); - }); +// it('should give at most the requested number of suggestions', function() { +// // helllo known to give at least 4 suggestions +// spellchecker.getSuggestions('helllo', 4).length.should.be.equal(4); +// spellchecker.getSuggestions('helllo', 1).length.should.be.equal(1); +// }); - it('should allow numerals', function() { - spellchecker.spellCheck('1').should.equal(true); - spellchecker.spellCheck('-100').should.equal(true); - spellchecker.spellCheck('3.14').should.equal(true); - }); +// it('should give suggestions which preserve case of first letter', function() { +// let suggestions = spellchecker.getSuggestions('carr', 4); +// suggestions.length.should.not.be.equal(0); +// let i; +// for (i = 0; i < suggestions.length; i++) { +// suggestions[i].charAt(0).should.be.equal('c'); +// } - it('should allow "Mattermost"', function() { - spellchecker.spellCheck('Mattermost').should.equal(true); - spellchecker.spellCheck('mattermost').should.equal(true); - }); +// suggestions = spellchecker.getSuggestions('Carr', 4); +// suggestions.length.should.not.be.equal(0); +// for (i = 0; i < suggestions.length; i++) { +// suggestions[i].charAt(0).should.be.equal('C'); +// } +// }); +// }); - it('should give at most the requested number of suggestions', function() { - // helllo known to give at least 4 suggestions - spellchecker.getSuggestions('helllo', 4).length.should.be.equal(4); - spellchecker.getSuggestions('helllo', 1).length.should.be.equal(1); - }); +// describe('en-GB', function() { +// const spellchecker = null; - it('should give suggestions which preserve case of first letter', function() { - let suggestions = spellchecker.getSuggestions('carr', 4); - suggestions.length.should.not.be.equal(0); - let i; - for (i = 0; i < suggestions.length; i++) { - suggestions[i].charAt(0).should.be.equal('c'); - } +// // before(function(done) { +// // spellchecker = new SpellChecker( +// // 'en-GB', +// // path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), +// // done +// // ); +// // }); - suggestions = spellchecker.getSuggestions('Carr', 4); - suggestions.length.should.not.be.equal(0); - for (i = 0; i < suggestions.length; i++) { - suggestions[i].charAt(0).should.be.equal('C'); - } - }); - }); +// it('should allow contractions', function() { +// spellchecker.spellCheck("shouldn't").should.equal(true); +// spellchecker.spellCheck('shouldn').should.equal(true); +// }); +// }); - describe('en-GB', function() { - let spellchecker = null; +// describe('de-DE', function() { +// const spellchecker = null; - before(function(done) { - spellchecker = new SpellChecker( - 'en-GB', - path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), - done - ); - }); +// // before(function(done) { +// // spellchecker = new SpellChecker( +// // 'de-DE', +// // path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), +// // done +// // ); +// // }); - it('should allow contractions', function() { - spellchecker.spellCheck("shouldn't").should.equal(true); - spellchecker.spellCheck('shouldn').should.equal(true); - }); - }); +// it('should spellcheck', function() { +// spellchecker.spellCheck('Guten').should.equal(true); +// spellchecker.spellCheck('tag').should.equal(true); +// }); - describe('ru-RU', function() { - let spellchecker = null; +// it('should allow numerals', function() { +// spellchecker.spellCheck('1').should.equal(true); +// spellchecker.spellCheck('-100').should.equal(true); +// spellchecker.spellCheck('3.14').should.equal(true); +// }); - before(function(done) { - spellchecker = new SpellChecker( - 'ru-RU', - path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), - done - ); - }); +// it('should give suggestions which preserve case of first letter', function() { +// let suggestions = spellchecker.getSuggestions('gutenn', 4); +// suggestions.length.should.not.be.equal(0); +// let i; +// for (i = 0; i < suggestions.length; i++) { +// suggestions[i].charAt(0).should.be.equal('g'); +// } - it('should spellcheck', function() { - spellchecker.spellCheck('русский').should.equal(true); - }); - it('should give suggestions', function() { - spellchecker.getSuggestions('руский', 1).length.should.be.equal(1); - }); - }); - - describe('sv-SE', function() { - let spellchecker = null; - - before(function(done) { - spellchecker = new SpellChecker( - 'sv-SE', - path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), - done - ); - }); - - it('should spellcheck', function() { - spellchecker.spellCheck('ändamålslös').should.equal(true); - spellchecker.spellCheck('ändamålslos').should.equal(false); - }); - it('should give suggestions', function() { - spellchecker.getSuggestions('ändamålslos', 1).length.should.be.equal(1); - }); - }); - - describe('uk-UA', function() { - let spellchecker = null; - - before(function(done) { - spellchecker = new SpellChecker( - 'uk-UA', - path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), - done - ); - }); - - it('should spellcheck', function() { - spellchecker.spellCheck('українська').should.equal(true); - }); - it('should give suggestions', function() { - spellchecker.getSuggestions('украінська', 1).length.should.be.equal(1); - }); - }); - - describe('de-DE', function() { - let spellchecker = null; - - before(function(done) { - spellchecker = new SpellChecker( - 'de-DE', - path.resolve(__dirname, '../../src/node_modules/simple-spellchecker/dict'), - done - ); - }); - - it('should spellcheck', function() { - spellchecker.spellCheck('Guten').should.equal(true); - spellchecker.spellCheck('tag').should.equal(true); - }); - - it('should allow numerals', function() { - spellchecker.spellCheck('1').should.equal(true); - spellchecker.spellCheck('-100').should.equal(true); - spellchecker.spellCheck('3.14').should.equal(true); - }); - - it('should give suggestions which preserve case of first letter', function() { - let suggestions = spellchecker.getSuggestions('gutenn', 4); - suggestions.length.should.not.be.equal(0); - let i; - for (i = 0; i < suggestions.length; i++) { - suggestions[i].charAt(0).should.be.equal('g'); - } - - suggestions = spellchecker.getSuggestions('Gutenn', 4); - suggestions.length.should.not.be.equal(0); - for (i = 0; i < suggestions.length; i++) { - suggestions[i].charAt(0).should.be.equal('G'); - } - }); - }); -}); +// suggestions = spellchecker.getSuggestions('Gutenn', 4); +// suggestions.length.should.not.be.equal(0); +// for (i = 0; i < suggestions.length; i++) { +// suggestions[i].charAt(0).should.be.equal('G'); +// } +// }); +// }); +// }); diff --git a/test/specs/utils/url_test.js b/test/specs/utils/url_test.js deleted file mode 100644 index dd90d3d3..00000000 --- a/test/specs/utils/url_test.js +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -'use strict'; - -import assert from 'assert'; - -import urlUtils from '../../../src/utils/url'; - -describe('url', () => { - describe('isValidURL', () => { - it('should be true for a valid web url', () => { - const testURL = 'https://developers.mattermost.com/'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for a valid, non-https web url', () => { - const testURL = 'http://developers.mattermost.com/'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for an invalid, self-defined, top-level domain', () => { - const testURL = 'https://www.example.x'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for a file download url', () => { - const testURL = 'https://community.mattermost.com/api/v4/files/ka3xbfmb3ffnmgdmww8otkidfw?download=1'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for a permalink url', () => { - const testURL = 'https://community.mattermost.com/test-channel/pl/pdqowkij47rmbyk78m5hwc7r6r'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for a valid, internal domain', () => { - const testURL = 'https://mattermost.company-internal'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for a second, valid internal domain', () => { - const testURL = 'https://serverXY/mattermost'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for a valid, non-https internal domain', () => { - const testURL = 'http://mattermost.local'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - it('should be true for a valid, non-https, ip address with port number', () => { - const testURL = 'http://localhost:8065'; - assert.equal(urlUtils.isValidURL(testURL), true); - }); - }); - describe('isValidURI', () => { - it('should be true for a deeplink url', () => { - const testURL = 'mattermost://community-release.mattermost.com/core/channels/developers'; - assert.equal(urlUtils.isValidURI(testURL), true); - }); - it('should be false for a malicious url', () => { - const testURL = String.raw`mattermost:///" --data-dir "\\deans-mbp\mattermost`; - assert.equal(urlUtils.isValidURI(testURL), false); - }); - }); - describe('isInternalURL', () => { - it('should be false for different hosts', () => { - const currentURL = new URL('http://localhost/team/channel1'); - const targetURL = new URL('http://example.com/team/channel2'); - const basename = '/'; - assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false); - }); - - it('should be false for same hosts, non-matching basename', () => { - const currentURL = new URL('http://localhost/subpath/team/channel1'); - const targetURL = new URL('http://localhost/team/channel2'); - const basename = '/subpath'; - assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false); - }); - - it('should be true for same hosts, matching basename', () => { - const currentURL = new URL('http://localhost/subpath/team/channel1'); - const targetURL = new URL('http://localhost/subpath/team/channel2'); - const basename = '/subpath'; - assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true); - }); - - it('should be true for same hosts, default basename', () => { - const currentURL = new URL('http://localhost/team/channel1'); - const targetURL = new URL('http://localhost/team/channel2'); - const basename = '/'; - assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true); - }); - - it('should be true for same hosts, default basename, empty target path', () => { - const currentURL = new URL('http://localhost/team/channel1'); - const targetURL = new URL('http://localhost/'); - const basename = '/'; - assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true); - }); - }); - - describe('getHost', () => { - it('should return the origin of a well formed url', () => { - const myurl = 'https://mattermost.com/download'; - assert.equal(urlUtils.getHost(myurl), 'https://mattermost.com'); - }); - - it('shoud raise an error on malformed urls', () => { - const myurl = 'http://example.com:-80/'; - assert.throws(() => urlUtils.getHost(myurl), Error); - }); - }); -}); diff --git a/test/specs/utils/util_test.js b/test/specs/utils/util_test.js new file mode 100644 index 00000000..00675b92 --- /dev/null +++ b/test/specs/utils/util_test.js @@ -0,0 +1,106 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +'use strict'; + +import assert from 'assert'; + +import urlUtils from '../../../src/common/utils/url'; + +describe('Utils', () => { + describe('isValidURL', () => { + it('should be true for a valid web url', () => { + const testURL = 'https://developers.mattermost.com/'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for a valid, non-https web url', () => { + const testURL = 'http://developers.mattermost.com/'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for an invalid, self-defined, top-level domain', () => { + const testURL = 'https://www.example.x'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for a file download url', () => { + const testURL = 'https://community.mattermost.com/api/v4/files/ka3xbfmb3ffnmgdmww8otkidfw?download=1'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for a permalink url', () => { + const testURL = 'https://community.mattermost.com/test-channel/pl/pdqowkij47rmbyk78m5hwc7r6r'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for a valid, internal domain', () => { + const testURL = 'https://mattermost.company-internal'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for a second, valid internal domain', () => { + const testURL = 'https://serverXY/mattermost'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for a valid, non-https internal domain', () => { + const testURL = 'http://mattermost.local'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + it('should be true for a valid, non-https, ip address with port number', () => { + const testURL = 'http://localhost:8065'; + assert.equal(urlUtils.isValidURL(testURL), true); + }); + }); + describe('isValidURI', () => { + it('should be true for a deeplink url', () => { + const testURL = 'mattermost://community-release.mattermost.com/core/channels/developers'; + assert.equal(urlUtils.isValidURI(testURL), true); + }); + it('should be false for a malicious url', () => { + const testURL = String.raw`mattermost:///" --data-dir "\\deans-mbp\mattermost`; + assert.equal(urlUtils.isValidURI(testURL), false); + }); + }); + describe('isInternalURL', () => { + it('should be false for different hosts', () => { + const currentURL = new URL('http://localhost/team/channel1'); + const targetURL = new URL('http://example.com/team/channel2'); + const basename = '/'; + assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false); + }); + + it('should be false for same hosts, non-matching basename', () => { + const currentURL = new URL('http://localhost/subpath/team/channel1'); + const targetURL = new URL('http://localhost/team/channel2'); + const basename = '/subpath'; + assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false); + }); + + it('should be true for same hosts, matching basename', () => { + const currentURL = new URL('http://localhost/subpath/team/channel1'); + const targetURL = new URL('http://localhost/subpath/team/channel2'); + const basename = '/subpath'; + assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true); + }); + + it('should be true for same hosts, default basename', () => { + const currentURL = new URL('http://localhost/team/channel1'); + const targetURL = new URL('http://localhost/team/channel2'); + const basename = '/'; + assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true); + }); + + it('should be true for same hosts, default basename, empty target path', () => { + const currentURL = new URL('http://localhost/team/channel1'); + const targetURL = new URL('http://localhost/'); + const basename = '/'; + assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true); + }); + }); + + describe('getHost', () => { + it('should return the origin of a well formed url', () => { + const myurl = 'https://mattermost.com/download'; + assert.equal(urlUtils.getHost(myurl), 'https://mattermost.com'); + }); + + it('shoud raise an error on malformed urls', () => { + const myurl = 'http://example.com:-80/'; + assert.throws(() => urlUtils.getHost(myurl), Error); + }); + }); +}); diff --git a/test/unit/index.js b/test/unit/index.js new file mode 100644 index 00000000..43cc0724 --- /dev/null +++ b/test/unit/index.js @@ -0,0 +1,4 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import './trusted_origins_test'; diff --git a/test/unit/trusted_origins_test.js b/test/unit/trusted_origins_test.js new file mode 100644 index 00000000..dfdea59d --- /dev/null +++ b/test/unit/trusted_origins_test.js @@ -0,0 +1,99 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +'use strict'; + +import assert from 'assert'; +import 'airbnb-js-shims/target/es2015'; + +import TrustedOriginsStore from 'main/trustedOrigins.js'; +import {BASIC_AUTH_PERMISSION} from 'common/permissions.js'; + +function mockTOS(fileName, returnvalue) { + const tos = new TrustedOriginsStore(fileName); + tos.readFromFile = () => { + return returnvalue; + }; + return tos; +} + +describe('Trusted Origins', () => { + describe('validate load', () => { + it('should be empty if there is no file', () => { + const tos = mockTOS('emptyfile', null); + tos.load(); + assert.deepEqual(tos.data.size, 0); + }); + + it('should throw an error if data isn\'t an object', () => { + const tos = mockTOS('notobject', 'this is not my object!'); + + assert.throws(tos.load, SyntaxError); + }); + + it('should throw an error if data isn\'t in the expected format', () => { + const tos = mockTOS('badobject', '{"https://mattermost.com": "this is not my object!"}'); + assert.throws(tos.load, /^Error: Provided TrustedOrigins file does not validate, using defaults instead\.$/); + }); + + it('should drop keys that aren\'t urls', () => { + const tos = mockTOS('badobject2', `{"this is not an uri": {"${BASIC_AUTH_PERMISSION}": true}}`); + tos.load(); + assert.equal(typeof tos.data['this is not an uri'], 'undefined'); + }); + + it('should contain valid data if everything goes right', () => { + const value = { + 'https://mattermost.com': { + [BASIC_AUTH_PERMISSION]: true, + }}; + const tos = mockTOS('okfile', JSON.stringify(value)); + tos.load(); + assert.deepEqual(Object.fromEntries(tos.data.entries()), value); + }); + }); + describe('validate testing permissions', () => { + const value = { + 'https://mattermost.com': { + [BASIC_AUTH_PERMISSION]: true, + }, + 'https://notmattermost.com': { + [BASIC_AUTH_PERMISSION]: false, + }, + }; + const tos = mockTOS('permission_test', JSON.stringify(value)); + tos.load(); + it('tos should contain 2 elements', () => { + assert.equal(tos.data.size, 2); + }); + it('should say ok if the permission is set', () => { + assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), true); + }); + it('should say ko if the permission is set to false', () => { + assert.equal(tos.checkPermission('https://notmattermost.com', BASIC_AUTH_PERMISSION), false); + }); + it('should say ko if the uri is not set', () => { + assert.equal(tos.checkPermission('https://undefined.com', BASIC_AUTH_PERMISSION), null); + }); + it('should say null if the permission is unknown', () => { + assert.equal(tos.checkPermission('https://mattermost.com'), null); + }); + }); + + describe('validate deleting permissions', () => { + const value = { + 'https://mattermost.com': { + [BASIC_AUTH_PERMISSION]: true, + }, + 'https://notmattermost.com': { + [BASIC_AUTH_PERMISSION]: false, + }, + }; + const tos = mockTOS('permission_test', JSON.stringify(value)); + tos.load(); + it('deleting revokes access', () => { + assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), true); + tos.delete('https://mattermost.com'); + assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), null); + }); + }); +}); diff --git a/webpack.config.base.js b/webpack.config.base.js index f0ed0e29..e9ef569a 100644 --- a/webpack.config.base.js +++ b/webpack.config.base.js @@ -6,19 +6,37 @@ /* eslint-disable import/no-commonjs */ 'use strict'; +const childProcess = require('child_process'); + const webpack = require('webpack'); +const path = require('path'); + +const VERSION = childProcess.execSync('git rev-parse --short HEAD').toString(); const isProduction = process.env.NODE_ENV === 'production'; +const codeDefinitions = { + __HASH_VERSION__: JSON.stringify(VERSION), +}; +codeDefinitions['process.env.NODE_ENV'] = JSON.stringify(process.env.NODE_ENV); + module.exports = { - // Some plugins cause errors on the app, so use few plugins. - // https://webpack.js.org/concepts/mode/#mode-production - mode: isProduction ? 'none' : 'development', - plugins: isProduction ? [ - new webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify('production')}), - ] : [], - devtool: isProduction ? false : '#inline-source-map', + // Some plugins cause errors on the app, so use few plugins. + // https://webpack.js.org/concepts/mode/#mode-production + mode: isProduction ? 'none' : 'development', + plugins: [ + new webpack.DefinePlugin(codeDefinitions), + ], + devtool: isProduction ? false : '#inline-source-map', + resolve: { + alias: { + renderer: path.resolve(__dirname, 'src/renderer'), + main: path.resolve(__dirname, './src/main'), + common: path.resolve(__dirname, './src/common'), + static: path.resolve(__dirname, './src/assets'), + }, + }, }; /* eslint-enable import/no-commonjs */ diff --git a/webpack.config.main.js b/webpack.config.main.js index ba32bd46..652007b4 100644 --- a/webpack.config.main.js +++ b/webpack.config.main.js @@ -1,6 +1,6 @@ -// Copyright (c) 2015-2016 Yuya Ochiai // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +// Copyright (c) 2015-2016 Yuya Ochiai // This files uses CommonJS. /* eslint-disable import/no-commonjs */ @@ -10,30 +10,59 @@ const path = require('path'); const merge = require('webpack-merge'); +const CopyPlugin = require('copy-webpack-plugin'); + const base = require('./webpack.config.base'); module.exports = merge(base, { - entry: './src/main.js', - output: { - path: path.join(__dirname, 'src'), - filename: '[name]_bundle.js', - }, - module: { - rules: [{ - test: /\.js?$/, - use: { - loader: 'babel-loader', - options: { - include: ['@babel/plugin-proposal-class-properties'] - } - }, - }], - }, - node: { - __filename: true, - __dirname: true, - }, - target: 'electron-main', + entry: { + index: './src/main/main.js', + preload: './src/main/preload/mattermost.js', + modalPreload: './src/main/preload/modalPreload.js', + finderPreload: './src/main/preload/finderPreload.js', + loadingScreenPreload: './src/main/preload/loadingScreenPreload.js', + }, + output: { + path: path.join(__dirname, 'dist/'), + filename: '[name].js', + }, + module: { + rules: [{ + test: /\.js?$/, + use: { + loader: 'babel-loader', + options: { + include: ['@babel/plugin-proposal-class-properties'], + }, + }, + }, { + test: /\.mp3$/, + use: { + loader: 'url-loader', + }, + }, + { + test: /\.node$/, + loader: 'awesome-node-loader', + options: { + name: '[name].[ext]', + rewritePath: path.resolve(__dirname, 'dist'), + }, + }], + }, + plugins: [ + new CopyPlugin({ + patterns: [{ + from: 'assets/**/*', + context: 'src', + }], + }), + ], + node: { + __filename: true, + __dirname: true, + }, + target: 'electron-main', }); /* eslint-enable import/no-commonjs */ diff --git a/webpack.config.renderer.js b/webpack.config.renderer.js index a9ecab49..a022fc57 100644 --- a/webpack.config.renderer.js +++ b/webpack.config.renderer.js @@ -7,72 +7,143 @@ 'use strict'; const path = require('path'); - +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const merge = require('webpack-merge'); const base = require('./webpack.config.base'); +const WEBSERVER_PORT = 9001; + module.exports = merge(base, { - entry: { - index: './src/browser/index.jsx', - settings: './src/browser/settings.jsx', - updater: './src/browser/updater.jsx', - 'webview/mattermost': './src/browser/webview/mattermost.js', - }, - output: { - path: path.join(__dirname, 'src/browser'), - publicPath: 'browser', - filename: '[name]_bundle.js', - }, - module: { - rules: [{ - test: /\.(js|jsx)?$/, - use: { - loader: 'babel-loader', - }, - }, { - test: /\.css$/, - use: [ - {loader: 'style-loader'}, - {loader: 'css-loader'}, - ], - }, { - test: /\.mp3$/, - use: { - loader: 'url-loader', - }, - }, { - test: /\.(svg)$/, - use: [ - { - loader: 'file-loader', - options: { - name: '[hash].[ext]', - publicPath: './', - }, - }, - {loader: 'image-webpack-loader'}, - ], - }, { - test: /\.(eot|ttf|woff|woff2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: '/../assets/fonts', - publicPath: './assets/fonts', - }, - }], - }, - node: { - __filename: true, - __dirname: true, - }, - target: 'electron-renderer', - devServer: { - contentBase: path.join(__dirname, 'src'), - inline: true, - publicPath: '/browser/', - }, + entry: { + index: './src/renderer/index.jsx', + settings: './src/renderer/settings.jsx', + urlView: './src/renderer/modals/urlView/urlView.jsx', + newServer: './src/renderer/modals/newServer/newServer.jsx', + loginModal: './src/renderer/modals/login/login.jsx', + permissionModal: './src/renderer/modals/permission/permission.jsx', + certificateModal: './src/renderer/modals/certificate/certificate.jsx', + finder: './src/renderer/modals/finder/index.jsx', + loadingScreen: './src/renderer/modals/loadingScreen/index.jsx', + }, + output: { + path: path.resolve(__dirname, 'dist/renderer'), + filename: '[name]_bundle.js', + }, + plugins: [ + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop App', + template: 'src/renderer/index.html', + chunks: ['index'], + filename: 'index.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['settings'], + filename: 'settings.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['urlView'], + filename: 'urlView.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['newServer'], + filename: 'newServer.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['loginModal'], + filename: 'loginModal.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['permissionModal'], + filename: 'permissionModal.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['certificateModal'], + filename: 'certificateModal.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['finder'], + filename: 'finder.html', + }), + new HtmlWebpackPlugin({ + title: 'Mattermost Desktop Settings', + template: 'src/renderer/index.html', + chunks: ['loadingScreen'], + filename: 'loadingScreen.html', + }), + new MiniCssExtractPlugin({ + filename: 'styles.[contenthash].css', + ignoreOrder: true, + chunkFilename: '[id].[contenthash].css', + }), + ], + module: { + rules: [{ + test: /\.(js|jsx)?$/, + use: { + loader: 'babel-loader', + }, + }, { + test: /\.css$/, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + ], + }, { + test: /\.mp3$/, + use: { + loader: 'url-loader', + }, + }, { + test: /\.(svg|gif)$/, + use: [ + { + loader: 'file-loader', + options: { + name: '[name].[ext]', + publicPath: './assets', + outputPath: '/../assets', + }, + }, + {loader: 'image-webpack-loader'}, + ], + }, { + test: /\.(eot|ttf|woff|woff2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file-loader', + options: { + name: '[name].[ext]', + outputPath: '/../assets/fonts', + publicPath: './assets/fonts', + }, + }], + }, + node: { + __filename: false, + __dirname: false, + }, + target: 'electron-renderer', + devServer: { + contentBase: 'src/assets', + contentBasePublicPath: '/assets', + inline: true, + publicPath: '/renderer/', + port: WEBSERVER_PORT, + }, }); /* eslint-enable import/no-commonjs */ diff --git a/webpack.config.test.js b/webpack.config.test.js new file mode 100644 index 00000000..380a3a63 --- /dev/null +++ b/webpack.config.test.js @@ -0,0 +1,55 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// This file uses CommonJS. +/* eslint-disable import/no-commonjs */ +'use strict'; + +const path = require('path'); + +const merge = require('webpack-merge'); + +const base = require('./webpack.config.base'); + +const WEBSERVER_PORT = 9001; + +module.exports = merge(base, { + entry: { + test: './test/unit/index.js', + }, + output: { + path: path.resolve(__dirname, 'dist/tests'), + filename: '[name]_bundle.js', + }, + module: { + rules: [{ + test: /\.(js|jsx)?$/, + use: ['babel-loader'], + }], + }, + externals: { + puppeteer: 'require("puppeteer")', + fs: 'require("fs")', + ws: 'require("ws")', + child_process: 'require("child_process")', + dns: 'require("dns")', + http2: 'require("http2")', + net: 'require("net")', + repl: 'require("repl")', + tls: 'require("tls")', + }, + node: { + __filename: false, + __dirname: false, + }, + devServer: { + contentBase: 'src/assets', + contentBasePublicPath: '/assets', + inline: true, + publicPath: '/renderer/', + port: WEBSERVER_PORT, + }, + target: 'electron-main', +}); + +/* eslint-enable import/no-commonjs */