diff --git a/package.json b/package.json index 821e1d9..58c2aed 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "@ant-design/icons": "^4.5.0", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.8.0", - "antd": "^4.13.0", + "@testing-library/user-event": "^12.8.1", + "antd": "^4.13.1", "async-mutex": "^0.3.1", "await-to-js": "^2.1.1", "base64-arraybuffer": "^0.2.0", @@ -29,7 +29,7 @@ "dayjs": "^1.10.4", "debug": "^4.3.1", "file-saver": "^2.0.5", - "i18next": "^19.9.0", + "i18next": "^19.9.1", "i18next-browser-languagedetector": "^6.0.1", "i18next-http-backend": "^1.1.1", "lodash-es": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f41b06..8bb9287 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,8 +2,8 @@ '@ant-design/icons': 4.5.0_react-dom@17.0.1+react@17.0.1 '@testing-library/jest-dom': 5.11.9 '@testing-library/react': 11.2.5_react-dom@17.0.1+react@17.0.1 - '@testing-library/user-event': 12.8.0 - antd: 4.13.0_89622fd8e4ec221151a62783d49305af + '@testing-library/user-event': 12.8.1 + antd: 4.13.1_89622fd8e4ec221151a62783d49305af async-mutex: 0.3.1 await-to-js: 2.1.1 base64-arraybuffer: 0.2.0 @@ -13,7 +13,7 @@ dayjs: 1.10.4 debug: 4.3.1 file-saver: 2.0.5 - i18next: 19.9.0 + i18next: 19.9.1 i18next-browser-languagedetector: 6.0.1 i18next-http-backend: 1.1.1 lodash-es: 4.17.21 @@ -22,7 +22,7 @@ react-chartjs-2: 2.11.1_6c446a34f83b2a92e3214f8b711c141a react-dom: 17.0.1_react@17.0.1 react-hotkeys: 2.0.0_react@17.0.1 - react-i18next: 11.8.8_i18next@19.9.0+react@17.0.1 + react-i18next: 11.8.8_i18next@19.9.1+react@17.0.1 react-linkify: 1.0.0-alpha react-redux: 7.2.2_380dc38591053d98779d1f25fc7202b9 react-router-dom: 5.2.0_react@17.0.1 @@ -56,7 +56,7 @@ '@types/semver': 7.3.4 '@types/uuid': 8.3.0 '@types/webpack-env': 1.16.0 - '@typescript-eslint/eslint-plugin': 4.15.3-alpha.17_bcfedfa8b2673ad3028fc37ce1448d88 + '@typescript-eslint/eslint-plugin': 4.15.3-alpha.17_0c05b3d2e4bc9c73120c3f1c1aed4425 '@typescript-eslint/parser': 4.15.3-alpha.17_eslint@7.21.0+typescript@4.1.5 antd-dayjs-webpack-plugin: 1.0.6_dayjs@1.10.4 babel-plugin-lodash: 3.3.4 @@ -90,7 +90,7 @@ dependencies: '@ant-design/colors': 6.0.0 '@ant-design/icons-svg': 4.1.0 - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 insert-css: 2.0.0 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -105,7 +105,7 @@ integrity: sha512-ZAKJcmr4DBV3NWr8wm2dCxNKN4eFrX+qCaPsuFejP6FRsf+m5OKxvCVi9bSp1lmKWeOI5yECAx5s0uFm4QHuPw== /@ant-design/react-slick/0.28.2: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 json2mq: 0.2.0 lodash: 4.17.21 @@ -137,10 +137,10 @@ /@babel/core/7.12.3: dependencies: '@babel/code-frame': 7.12.13 - '@babel/generator': 7.13.0 + '@babel/generator': 7.13.9 '@babel/helper-module-transforms': 7.13.0 '@babel/helpers': 7.13.0 - '@babel/parser': 7.13.4 + '@babel/parser': 7.13.9 '@babel/template': 7.12.13 '@babel/traverse': 7.13.0 '@babel/types': 7.13.0 @@ -149,7 +149,7 @@ gensync: 1.0.0-beta.2 json5: 2.2.0 lodash: 4.17.21 - resolve: 1.20.0 + resolve: 1.18.1 semver: 5.7.1 source-map: 0.5.7 dev: true @@ -157,14 +157,14 @@ node: '>=6.9.0' resolution: integrity: sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== - /@babel/generator/7.13.0: + /@babel/generator/7.13.9: dependencies: '@babel/types': 7.13.0 jsesc: 2.5.2 source-map: 0.5.7 dev: true resolution: - integrity: sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw== + integrity: sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== /@babel/helper-annotate-as-pure/7.12.13: dependencies: '@babel/types': 7.13.0 @@ -213,7 +213,7 @@ '@babel/core': ^7.0.0 resolution: integrity: sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg== - /@babel/helper-define-polyfill-provider/0.1.4_@babel+core@7.12.3: + /@babel/helper-define-polyfill-provider/0.1.5_@babel+core@7.12.3: dependencies: '@babel/core': 7.12.3 '@babel/helper-compilation-targets': 7.13.8_@babel+core@7.12.3 @@ -222,13 +222,13 @@ '@babel/traverse': 7.13.0 debug: 4.3.1 lodash.debounce: 4.0.8 - resolve: 1.20.0 + resolve: 1.18.1 semver: 6.3.0 dev: true peerDependencies: '@babel/core': ^7.4.0-0 resolution: - integrity: sha512-K5V2GaQZ1gpB+FTXM4AFVG2p1zzhm67n9wrQCJYNzvuLzQybhJyftW7qeDd2uUxPDNdl5Rkon1rOAeUeNDZ28Q== + integrity: sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg== /@babel/helper-explode-assignable-expression/7.13.0: dependencies: '@babel/types': 7.13.0 @@ -358,13 +358,13 @@ js-tokens: 4.0.0 resolution: integrity: sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw== - /@babel/parser/7.13.4: + /@babel/parser/7.13.9: dev: true engines: node: '>=6.0.0' hasBin: true resolution: - integrity: sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA== + integrity: sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw== /@babel/plugin-proposal-async-generator-functions/7.13.8_@babel+core@7.12.3: dependencies: '@babel/core': 7.12.3 @@ -1055,7 +1055,7 @@ '@babel/core': 7.12.3 '@babel/helper-module-imports': 7.12.13 '@babel/helper-plugin-utils': 7.13.0 - resolve: 1.20.0 + resolve: 1.18.1 semver: 5.7.1 dev: true peerDependencies: @@ -1147,16 +1147,16 @@ '@babel/helper-plugin-utils': 7.13.0 '@babel/helper-validator-option': 7.12.17 '@babel/plugin-proposal-async-generator-functions': 7.13.8_@babel+core@7.12.3 - '@babel/plugin-proposal-class-properties': 7.13.0_@babel+core@7.12.3 + '@babel/plugin-proposal-class-properties': 7.12.1_@babel+core@7.12.3 '@babel/plugin-proposal-dynamic-import': 7.13.8_@babel+core@7.12.3 '@babel/plugin-proposal-export-namespace-from': 7.12.13_@babel+core@7.12.3 '@babel/plugin-proposal-json-strings': 7.13.8_@babel+core@7.12.3 '@babel/plugin-proposal-logical-assignment-operators': 7.13.8_@babel+core@7.12.3 - '@babel/plugin-proposal-nullish-coalescing-operator': 7.13.8_@babel+core@7.12.3 - '@babel/plugin-proposal-numeric-separator': 7.12.13_@babel+core@7.12.3 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.12.1_@babel+core@7.12.3 + '@babel/plugin-proposal-numeric-separator': 7.12.1_@babel+core@7.12.3 '@babel/plugin-proposal-object-rest-spread': 7.13.8_@babel+core@7.12.3 '@babel/plugin-proposal-optional-catch-binding': 7.13.8_@babel+core@7.12.3 - '@babel/plugin-proposal-optional-chaining': 7.13.8_@babel+core@7.12.3 + '@babel/plugin-proposal-optional-chaining': 7.12.1_@babel+core@7.12.3 '@babel/plugin-proposal-private-methods': 7.13.0_@babel+core@7.12.3 '@babel/plugin-proposal-unicode-property-regex': 7.12.13_@babel+core@7.12.3 '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.12.3 @@ -1212,7 +1212,7 @@ '@babel/core': ^7.0.0-0 resolution: integrity: sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg== - /@babel/preset-env/7.13.8_@babel+core@7.12.3: + /@babel/preset-env/7.13.9_@babel+core@7.12.3: dependencies: '@babel/compat-data': 7.13.8 '@babel/core': 7.12.3 @@ -1278,16 +1278,16 @@ '@babel/plugin-transform-unicode-regex': 7.12.13_@babel+core@7.12.3 '@babel/preset-modules': 0.1.4_@babel+core@7.12.3 '@babel/types': 7.13.0 - babel-plugin-polyfill-corejs2: 0.1.8_@babel+core@7.12.3 - babel-plugin-polyfill-corejs3: 0.1.6_@babel+core@7.12.3 - babel-plugin-polyfill-regenerator: 0.1.5_@babel+core@7.12.3 + babel-plugin-polyfill-corejs2: 0.1.10_@babel+core@7.12.3 + babel-plugin-polyfill-corejs3: 0.1.7_@babel+core@7.12.3 + babel-plugin-polyfill-regenerator: 0.1.6_@babel+core@7.12.3 core-js-compat: 3.9.1 semver: 6.3.0 dev: true peerDependencies: '@babel/core': ^7.0.0-0 resolution: - integrity: sha512-Sso1xOpV4S3ofnxW2DsWTE5ziRk62jEAKLGuQ+EJHC+YHTbFG38QUTixO3JVa1cYET9gkJhO1pMu+/+2dDhKvw== + integrity: sha512-mcsHUlh2rIhViqMG823JpscLMesRt3QbMsv1+jhopXEb3W2wXvQ9QoiOlZI9ZbR3XqPtaFpZwEZKYqGJnGMZTQ== /@babel/preset-modules/0.1.4_@babel+core@7.12.3: dependencies: '@babel/core': 7.12.3 @@ -1318,7 +1318,7 @@ dependencies: '@babel/core': 7.12.3 '@babel/helper-plugin-utils': 7.13.0 - '@babel/plugin-transform-react-display-name': 7.12.13_@babel+core@7.12.3 + '@babel/plugin-transform-react-display-name': 7.12.1_@babel+core@7.12.3 '@babel/plugin-transform-react-jsx': 7.12.17_@babel+core@7.12.3 '@babel/plugin-transform-react-jsx-development': 7.12.17_@babel+core@7.12.3 '@babel/plugin-transform-react-jsx-self': 7.12.13_@babel+core@7.12.3 @@ -1339,27 +1339,27 @@ '@babel/core': ^7.0.0-0 resolution: integrity: sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw== - /@babel/runtime-corejs3/7.13.8: + /@babel/runtime-corejs3/7.13.9: dependencies: core-js-pure: 3.9.1 regenerator-runtime: 0.13.7 resolution: - integrity: sha512-iaInhjy1BbDnqc7pZiIXAfWvBnczgWobHceR4Wkhs5tWZG8aIazBYH0Vo73lixecHKh3Vy9yqbQBqVDrmcVDlQ== + integrity: sha512-p6WSr71+5u/VBf1KDS/Y4dK3ZwbV+DD6wQO3X2EbUVluEOiyXUk09DzcwSaUH4WomYXrEPC+i2rqzuthhZhOJw== /@babel/runtime/7.12.1: dependencies: regenerator-runtime: 0.13.7 dev: true resolution: integrity: sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== - /@babel/runtime/7.13.8: + /@babel/runtime/7.13.9: dependencies: regenerator-runtime: 0.13.7 resolution: - integrity: sha512-CwQljpw6qSayc0fRG1soxHAKs1CnQMOChm4mlQP6My0kf9upVGizj/KhlTTgyUnETmHpcUXjaluNAkteRFuafg== + integrity: sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA== /@babel/template/7.12.13: dependencies: '@babel/code-frame': 7.12.13 - '@babel/parser': 7.13.4 + '@babel/parser': 7.13.9 '@babel/types': 7.13.0 dev: true resolution: @@ -1367,10 +1367,10 @@ /@babel/traverse/7.13.0: dependencies: '@babel/code-frame': 7.12.13 - '@babel/generator': 7.13.0 + '@babel/generator': 7.13.9 '@babel/helper-function-name': 7.12.13 '@babel/helper-split-export-declaration': 7.12.13 - '@babel/parser': 7.13.4 + '@babel/parser': 7.13.9 '@babel/types': 7.13.0 debug: 4.3.1 globals: 11.12.0 @@ -1772,7 +1772,7 @@ '@types/resolve': 0.0.8 builtin-modules: 3.2.0 is-module: 1.0.0 - resolve: 1.20.0 + resolve: 1.18.1 rollup: 1.32.1 dev: true engines: @@ -1929,7 +1929,7 @@ dependencies: '@babel/core': 7.12.3 '@babel/plugin-transform-react-constant-elements': 7.12.13_@babel+core@7.12.3 - '@babel/preset-env': 7.13.8_@babel+core@7.12.3 + '@babel/preset-env': 7.13.9_@babel+core@7.12.3 '@babel/preset-react': 7.12.13_@babel+core@7.12.3 '@svgr/core': 5.5.0 '@svgr/plugin-jsx': 5.5.0 @@ -1943,7 +1943,7 @@ /@testing-library/dom/7.29.6: dependencies: '@babel/code-frame': 7.12.13 - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 '@types/aria-query': 4.2.1 aria-query: 4.2.2 chalk: 4.1.0 @@ -1957,7 +1957,7 @@ integrity: sha512-vzTsAXa439ptdvav/4lsKRcGpAQX7b6wBIqia7+iNzqGJ5zjswApxA6jDAsexrc6ue9krWcbh8o+LYkBXW+GCQ== /@testing-library/jest-dom/5.11.9: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 '@types/testing-library__jest-dom': 5.9.5 aria-query: 4.2.2 chalk: 3.0.0 @@ -1974,7 +1974,7 @@ integrity: sha512-Mn2gnA9d1wStlAIT2NU8J15LNob0YFBVjs2aEQ3j8rsfRQo+lAs7/ui1i2TGaJjapLmuNPLTsrm+nPjmZDwpcQ== /@testing-library/react/11.2.5_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 '@testing-library/dom': 7.29.6 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -1986,9 +1986,9 @@ react-dom: '*' resolution: integrity: sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== - /@testing-library/user-event/12.8.0: + /@testing-library/user-event/12.8.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 dev: false engines: node: '>=10' @@ -1996,7 +1996,7 @@ peerDependencies: '@testing-library/dom': '>=7.21.4' resolution: - integrity: sha512-5+k4U3X6XaFDSBSu6tsD02HVfzuOiPcygQmmYFE2aQQ0e5wRSxWRoU80UH1msa9Q6wuxa0BQsAmwAAAMydcscg== + integrity: sha512-u521YhkCKip0DQNDpfj9V97PU7UlCTkW5jURUD4JipuVe/xDJ32dJSIHlT2pqAs/I91OFB8p6LtqaLZpOu8BWQ== /@types/anymatch/1.3.1: dev: true resolution: @@ -2007,7 +2007,7 @@ integrity: sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg== /@types/babel__core/7.1.12: dependencies: - '@babel/parser': 7.13.4 + '@babel/parser': 7.13.9 '@babel/types': 7.13.0 '@types/babel__generator': 7.6.2 '@types/babel__template': 7.4.0 @@ -2023,7 +2023,7 @@ integrity: sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== /@types/babel__template/7.4.0: dependencies: - '@babel/parser': 7.13.4 + '@babel/parser': 7.13.9 '@babel/types': 7.13.0 dev: true resolution: @@ -2145,10 +2145,10 @@ dev: true resolution: integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - /@types/prettier/2.2.1: + /@types/prettier/2.2.2: dev: true resolution: - integrity: sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw== + integrity: sha512-i99hy7Ki19EqVOl77WplDrvgNugHnsSjECVR/wUrzw2TJXz1zlUfT2ngGckR6xN7yFYaijsMAqPkOLx9HgUqHg== /@types/prop-types/15.7.3: dev: true resolution: @@ -2182,17 +2182,17 @@ dependencies: '@types/history': 4.7.8 '@types/react': 17.0.2 - '@types/react-router': 5.1.11 + '@types/react-router': 5.1.12 dev: true resolution: integrity: sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg== - /@types/react-router/5.1.11: + /@types/react-router/5.1.12: dependencies: '@types/history': 4.7.8 '@types/react': 17.0.2 dev: true resolution: - integrity: sha512-ofHbZMlp0Y2baOHgsWBQ4K3AttxY61bDMkwTiBOkPg7U6C/3UwwB5WaIx28JmSVi/eX3uFEMRo61BV22fDQIvg== + integrity: sha512-0bhXQwHYfMeJlCh7mGhc0VJTRm0Gk+Z8T00aiP4702mDUuLs9SMhnd2DitpjWFjdOecx2UXtICK14H9iMnziGA== /@types/react-timeago/4.1.2: dependencies: '@types/react': 17.0.2 @@ -2275,32 +2275,7 @@ '@types/yargs-parser': 20.2.0 resolution: integrity: sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ== - /@typescript-eslint/eslint-plugin/4.15.2_82d802bc1360aa6114c0ff047240d01c: - dependencies: - '@typescript-eslint/experimental-utils': 4.15.2_eslint@7.21.0+typescript@4.1.5 - '@typescript-eslint/parser': 4.15.2_eslint@7.21.0+typescript@4.1.5 - '@typescript-eslint/scope-manager': 4.15.2 - debug: 4.3.1 - eslint: 7.21.0 - functional-red-black-tree: 1.0.1 - lodash: 4.17.21 - regexpp: 3.1.0 - semver: 7.3.4 - tsutils: 3.20.0_typescript@4.1.5 - typescript: 4.1.5 - dev: true - engines: - node: ^10.12.0 || >=12.0.0 - peerDependencies: - '@typescript-eslint/parser': ^4.0.0 - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - resolution: - integrity: sha512-uiQQeu9tWl3f1+oK0yoAv9lt/KXO24iafxgQTkIYO/kitruILGx3uH+QtIAHqxFV+yIsdnJH+alel9KuE3J15Q== - /@typescript-eslint/eslint-plugin/4.15.3-alpha.17_bcfedfa8b2673ad3028fc37ce1448d88: + /@typescript-eslint/eslint-plugin/4.15.3-alpha.17_0c05b3d2e4bc9c73120c3f1c1aed4425: dependencies: '@typescript-eslint/experimental-utils': 4.15.3-alpha.17_eslint@7.21.0+typescript@4.1.5 '@typescript-eslint/parser': 4.15.3-alpha.17_eslint@7.21.0+typescript@4.1.5 @@ -2325,6 +2300,31 @@ optional: true resolution: integrity: sha512-gyG/9a5FRxAEDOMJvsu/dBOAnXobwV21SG2cmOT/hT1Pu8vbwiRNOsGQbWIlrM9z0NJC6mM/EsN9nSjzVDnXcw== + /@typescript-eslint/eslint-plugin/4.16.1_9c4985f94498b1423f0e374931685aad: + dependencies: + '@typescript-eslint/experimental-utils': 4.16.1_eslint@7.21.0+typescript@4.1.5 + '@typescript-eslint/parser': 4.16.1_eslint@7.21.0+typescript@4.1.5 + '@typescript-eslint/scope-manager': 4.16.1 + debug: 4.3.1 + eslint: 7.21.0 + functional-red-black-tree: 1.0.1 + lodash: 4.17.21 + regexpp: 3.1.0 + semver: 7.3.4 + tsutils: 3.20.0_typescript@4.1.5 + typescript: 4.1.5 + dev: true + engines: + node: ^10.12.0 || >=12.0.0 + peerDependencies: + '@typescript-eslint/parser': ^4.0.0 + eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + resolution: + integrity: sha512-SK777klBdlkUZpZLC1mPvyOWk9yAFCWmug13eAjVQ4/Q1LATE/NbcQL1xDHkptQkZOLnPmLUA1Y54m8dqYwnoQ== /@typescript-eslint/experimental-utils/3.10.1_eslint@7.21.0+typescript@4.1.5: dependencies: '@types/json-schema': 7.0.7 @@ -2341,23 +2341,6 @@ typescript: '*' resolution: integrity: sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== - /@typescript-eslint/experimental-utils/4.15.2_eslint@7.21.0+typescript@4.1.5: - dependencies: - '@types/json-schema': 7.0.7 - '@typescript-eslint/scope-manager': 4.15.2 - '@typescript-eslint/types': 4.15.2 - '@typescript-eslint/typescript-estree': 4.15.2_typescript@4.1.5 - eslint: 7.21.0 - eslint-scope: 5.1.1 - eslint-utils: 2.1.0 - dev: true - engines: - node: ^10.12.0 || >=12.0.0 - peerDependencies: - eslint: '*' - typescript: '*' - resolution: - integrity: sha512-Fxoshw8+R5X3/Vmqwsjc8nRO/7iTysRtDqx6rlfLZ7HbT8TZhPeQqbPjTyk2RheH3L8afumecTQnUc9EeXxohQ== /@typescript-eslint/experimental-utils/4.15.3-alpha.17_eslint@7.21.0+typescript@4.1.5: dependencies: '@types/json-schema': 7.0.7 @@ -2375,25 +2358,23 @@ typescript: '*' resolution: integrity: sha512-m/F2+wq9Ijfdce7GMicfmNn1WAlp5xAT0+wDx3iVU5+uQfWuw0eA42IdlOPHsG7cnWRAFHhMP2ptXJR0lI3oPQ== - /@typescript-eslint/parser/4.15.2_eslint@7.21.0+typescript@4.1.5: + /@typescript-eslint/experimental-utils/4.16.1_eslint@7.21.0+typescript@4.1.5: dependencies: - '@typescript-eslint/scope-manager': 4.15.2 - '@typescript-eslint/types': 4.15.2 - '@typescript-eslint/typescript-estree': 4.15.2_typescript@4.1.5 - debug: 4.3.1 + '@types/json-schema': 7.0.7 + '@typescript-eslint/scope-manager': 4.16.1 + '@typescript-eslint/types': 4.16.1 + '@typescript-eslint/typescript-estree': 4.16.1_typescript@4.1.5 eslint: 7.21.0 - typescript: 4.1.5 + eslint-scope: 5.1.1 + eslint-utils: 2.1.0 dev: true engines: node: ^10.12.0 || >=12.0.0 peerDependencies: - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: '*' typescript: '*' - peerDependenciesMeta: - typescript: - optional: true resolution: - integrity: sha512-SHeF8xbsC6z2FKXsaTb1tBCf0QZsjJ94H6Bo51Y1aVEZ4XAefaw5ZAilMoDPlGghe+qtq7XdTiDlGfVTOmvA+Q== + integrity: sha512-0Hm3LSlMYFK17jO4iY3un1Ve9x1zLNn4EM50Lia+0EV99NdbK+cn0er7HC7IvBA23mBg3P+8dUkMXy4leL33UQ== /@typescript-eslint/parser/4.15.3-alpha.17_eslint@7.21.0+typescript@4.1.5: dependencies: '@typescript-eslint/scope-manager': 4.15.3-alpha.17 @@ -2413,15 +2394,25 @@ optional: true resolution: integrity: sha512-J7c6MXTnPM48ofQfba/3hd3wYuo1LmYW9F6RMscD3WdScpljMQ8XDL0XGg1js4184aywG2NgnKQT35+kELgS4Q== - /@typescript-eslint/scope-manager/4.15.2: + /@typescript-eslint/parser/4.16.1_eslint@7.21.0+typescript@4.1.5: dependencies: - '@typescript-eslint/types': 4.15.2 - '@typescript-eslint/visitor-keys': 4.15.2 + '@typescript-eslint/scope-manager': 4.16.1 + '@typescript-eslint/types': 4.16.1 + '@typescript-eslint/typescript-estree': 4.16.1_typescript@4.1.5 + debug: 4.3.1 + eslint: 7.21.0 + typescript: 4.1.5 dev: true engines: - node: ^8.10.0 || ^10.13.0 || >=11.10.1 + node: ^10.12.0 || >=12.0.0 + peerDependencies: + eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true resolution: - integrity: sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ== + integrity: sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg== /@typescript-eslint/scope-manager/4.15.3-alpha.17: dependencies: '@typescript-eslint/types': 4.15.3-alpha.17 @@ -2431,24 +2422,33 @@ node: ^8.10.0 || ^10.13.0 || >=11.10.1 resolution: integrity: sha512-kyn1Q44UWK0c/Y+SwZl4bmH59nRA7nG7YHNZvt+hVuCJOpaAgBYVKDMGZnwFTOCBRCUyyjksH145a2Xucdh72A== + /@typescript-eslint/scope-manager/4.16.1: + dependencies: + '@typescript-eslint/types': 4.16.1 + '@typescript-eslint/visitor-keys': 4.16.1 + dev: true + engines: + node: ^8.10.0 || ^10.13.0 || >=11.10.1 + resolution: + integrity: sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw== /@typescript-eslint/types/3.10.1: dev: true engines: node: ^8.10.0 || ^10.13.0 || >=11.10.1 resolution: integrity: sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== - /@typescript-eslint/types/4.15.2: - dev: true - engines: - node: ^8.10.0 || ^10.13.0 || >=11.10.1 - resolution: - integrity: sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ== /@typescript-eslint/types/4.15.3-alpha.17: dev: true engines: node: ^8.10.0 || ^10.13.0 || >=11.10.1 resolution: integrity: sha512-xM7Ico8kknqkFpbkIEQeagwR2DlHQbWAXKivzyDPbQFuntQ2Hv/rTthnUP88aCiD7gUJCz/IXofKD79Ky3U5dg== + /@typescript-eslint/types/4.16.1: + dev: true + engines: + node: ^8.10.0 || ^10.13.0 || >=11.10.1 + resolution: + integrity: sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA== /@typescript-eslint/typescript-estree/3.10.1_typescript@4.1.5: dependencies: '@typescript-eslint/types': 3.10.1 @@ -2470,26 +2470,6 @@ optional: true resolution: integrity: sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== - /@typescript-eslint/typescript-estree/4.15.2_typescript@4.1.5: - dependencies: - '@typescript-eslint/types': 4.15.2 - '@typescript-eslint/visitor-keys': 4.15.2 - debug: 4.3.1 - globby: 11.0.2 - is-glob: 4.0.1 - semver: 7.3.4 - tsutils: 3.20.0_typescript@4.1.5 - typescript: 4.1.5 - dev: true - engines: - node: ^10.12.0 || >=12.0.0 - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - resolution: - integrity: sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw== /@typescript-eslint/typescript-estree/4.15.3-alpha.17_typescript@4.1.5: dependencies: '@typescript-eslint/types': 4.15.3-alpha.17 @@ -2510,6 +2490,26 @@ optional: true resolution: integrity: sha512-kH7r/enZ98KxNfFg7C3VqswVHmp47ZNlYzzGG77NoFPaHVEtOl2aIULH7FrHYn+/mydeqKRWIk/Gpff1/cbVkg== + /@typescript-eslint/typescript-estree/4.16.1_typescript@4.1.5: + dependencies: + '@typescript-eslint/types': 4.16.1 + '@typescript-eslint/visitor-keys': 4.16.1 + debug: 4.3.1 + globby: 11.0.2 + is-glob: 4.0.1 + semver: 7.3.4 + tsutils: 3.20.0_typescript@4.1.5 + typescript: 4.1.5 + dev: true + engines: + node: ^10.12.0 || >=12.0.0 + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + resolution: + integrity: sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg== /@typescript-eslint/visitor-keys/3.10.1: dependencies: eslint-visitor-keys: 1.3.0 @@ -2518,15 +2518,6 @@ node: ^8.10.0 || ^10.13.0 || >=11.10.1 resolution: integrity: sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== - /@typescript-eslint/visitor-keys/4.15.2: - dependencies: - '@typescript-eslint/types': 4.15.2 - eslint-visitor-keys: 2.0.0 - dev: true - engines: - node: ^8.10.0 || ^10.13.0 || >=11.10.1 - resolution: - integrity: sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg== /@typescript-eslint/visitor-keys/4.15.3-alpha.17: dependencies: '@typescript-eslint/types': 4.15.3-alpha.17 @@ -2536,6 +2527,15 @@ node: ^8.10.0 || ^10.13.0 || >=11.10.1 resolution: integrity: sha512-koywimhlOO1y5TtoiM9W5D7uxeGchhbvw28+4l3OiaC6MJ9qwqW0XpaPFsziR6M+vy4LijZLpkiUmUZwDh+xrw== + /@typescript-eslint/visitor-keys/4.16.1: + dependencies: + '@typescript-eslint/types': 4.16.1 + eslint-visitor-keys: 2.0.0 + dev: true + engines: + node: ^8.10.0 || ^10.13.0 || >=11.10.1 + resolution: + integrity: sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w== /@webassemblyjs/ast/1.9.0: dependencies: '@webassemblyjs/helper-module-context': 1.9.0 @@ -2860,12 +2860,12 @@ dayjs: '*' resolution: integrity: sha512-UlK3BfA0iE2c5+Zz/Bd2iPAkT6cICtrKG4/swSik5MZweBHtgmu1aUQCHvICdiv39EAShdZy/edfP6mlkS/xXg== - /antd/4.13.0_89622fd8e4ec221151a62783d49305af: + /antd/4.13.1_89622fd8e4ec221151a62783d49305af: dependencies: '@ant-design/colors': 6.0.0 '@ant-design/icons': 4.5.0_react-dom@17.0.1+react@17.0.1 '@ant-design/react-slick': 0.28.2 - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 array-tree-filter: 2.1.0 classnames: 2.2.6 copy-to-clipboard: 3.3.1 @@ -2874,33 +2874,33 @@ rc-cascader: 1.4.2_react-dom@17.0.1+react@17.0.1 rc-checkbox: 2.3.2_react-dom@17.0.1+react@17.0.1 rc-collapse: 3.1.0_react-dom@17.0.1+react@17.0.1 - rc-dialog: 8.5.1_react-dom@17.0.1+react@17.0.1 + rc-dialog: 8.5.2_react-dom@17.0.1+react@17.0.1 rc-drawer: 4.3.1_react-dom@17.0.1+react@17.0.1 rc-dropdown: 3.2.0_react-dom@17.0.1+react@17.0.1 rc-field-form: 1.19.0_react-dom@17.0.1+react@17.0.1 rc-image: 5.2.3_react-dom@17.0.1+react@17.0.1 - rc-input-number: 7.0.0_react-dom@17.0.1+react@17.0.1 + rc-input-number: 7.0.1_react-dom@17.0.1+react@17.0.1 rc-mentions: 1.5.3_react-dom@17.0.1+react@17.0.1 rc-menu: 8.10.6_react-dom@17.0.1+react@17.0.1 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 - rc-notification: 4.5.4_react-dom@17.0.1+react@17.0.1 - rc-pagination: 3.1.3_react-dom@17.0.1+react@17.0.1 + rc-notification: 4.5.5_react-dom@17.0.1+react@17.0.1 + rc-pagination: 3.1.4_react-dom@17.0.1+react@17.0.1 rc-picker: 2.5.7_89622fd8e4ec221151a62783d49305af rc-progress: 3.1.3_react-dom@17.0.1+react@17.0.1 rc-rate: 2.9.1_react-dom@17.0.1+react@17.0.1 rc-resize-observer: 1.0.0_react-dom@17.0.1+react@17.0.1 - rc-select: 12.1.3_react-dom@17.0.1+react@17.0.1 + rc-select: 12.1.5_react-dom@17.0.1+react@17.0.1 rc-slider: 9.7.1_react-dom@17.0.1+react@17.0.1 rc-steps: 4.1.3_react-dom@17.0.1+react@17.0.1 rc-switch: 3.2.2_react-dom@17.0.1+react@17.0.1 rc-table: 7.13.1_react-dom@17.0.1+react@17.0.1 rc-tabs: 11.7.3_react-dom@17.0.1+react@17.0.1 rc-textarea: 0.3.4_react-dom@17.0.1+react@17.0.1 - rc-tooltip: 5.0.2_react-dom@17.0.1+react@17.0.1 - rc-tree: 4.1.2_react-dom@17.0.1+react@17.0.1 + rc-tooltip: 5.1.0_react-dom@17.0.1+react@17.0.1 + rc-tree: 4.1.3_react-dom@17.0.1+react@17.0.1 rc-tree-select: 4.3.0_react-dom@17.0.1+react@17.0.1 rc-trigger: 5.2.3_react-dom@17.0.1+react@17.0.1 - rc-upload: 4.0.0-alpha.6_react-dom@17.0.1+react@17.0.1 + rc-upload: 4.0.1_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -2912,7 +2912,7 @@ react: '>=16.9.0' react-dom: '>=16.9.0' resolution: - integrity: sha512-+lwc8QmBh4g3VxXeIiMUTXc2u2HeKUC2RKUyK2mUBtOBmT8aqs/oEDdj7g146vQk2M/WHnqNo4cfBlDQk5rbMg== + integrity: sha512-KaveTRGKOFrbas3FO0ktXaZrSMDpDWz3FpuLbafV7R973BNz2yo+IG8ia2KPEfpRtGYUvpnKlDUNQZWhotoYhQ== /anymatch/2.0.0: dependencies: micromatch: 3.1.10 @@ -2941,8 +2941,8 @@ integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== /aria-query/4.2.2: dependencies: - '@babel/runtime': 7.13.8 - '@babel/runtime-corejs3': 7.13.8 + '@babel/runtime': 7.13.9 + '@babel/runtime-corejs3': 7.13.9 engines: node: '>=6.0' resolution: @@ -2981,7 +2981,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 get-intrinsic: 1.1.1 is-string: 1.0.5 dev: true @@ -3023,7 +3023,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 dev: true engines: node: '>= 0.4' @@ -3033,7 +3033,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 function-bind: 1.1.1 dev: true engines: @@ -3137,7 +3137,7 @@ /autoprefixer/9.8.6: dependencies: browserslist: 4.16.3 - caniuse-lite: 1.0.30001192 + caniuse-lite: 1.0.30001196 colorette: 1.2.2 normalize-range: 0.1.2 num2fraction: 1.2.2 @@ -3161,12 +3161,12 @@ dev: true resolution: integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - /axe-core/4.1.2: + /axe-core/4.1.3: dev: true engines: node: '>=4' resolution: - integrity: sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg== + integrity: sha512-vwPpH4Aj4122EW38mxO/fxhGKtwWTMLDIJfZ1He0Edbtjcfna/R3YB67yVhezUMzqc3Jr3+Ii50KRntlENL4xQ== /axobject-query/2.2.0: dev: true resolution: @@ -3174,12 +3174,12 @@ /babel-eslint/10.1.0_eslint@7.21.0: dependencies: '@babel/code-frame': 7.12.13 - '@babel/parser': 7.13.4 + '@babel/parser': 7.13.9 '@babel/traverse': 7.13.0 '@babel/types': 7.13.0 eslint: 7.21.0 eslint-visitor-keys: 1.3.0 - resolve: 1.20.0 + resolve: 1.18.1 deprecated: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates. dev: true engines: @@ -3272,9 +3272,9 @@ integrity: sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg== /babel-plugin-macros/2.8.0: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.12.1 cosmiconfig: 6.0.0 - resolve: 1.20.0 + resolve: 1.18.1 dev: true resolution: integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== @@ -3286,36 +3286,36 @@ '@babel/core': ^7.1.0 resolution: integrity: sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw== - /babel-plugin-polyfill-corejs2/0.1.8_@babel+core@7.12.3: + /babel-plugin-polyfill-corejs2/0.1.10_@babel+core@7.12.3: dependencies: '@babel/compat-data': 7.13.8 '@babel/core': 7.12.3 - '@babel/helper-define-polyfill-provider': 0.1.4_@babel+core@7.12.3 + '@babel/helper-define-polyfill-provider': 0.1.5_@babel+core@7.12.3 semver: 6.3.0 dev: true peerDependencies: '@babel/core': ^7.0.0-0 resolution: - integrity: sha512-kB5/xNR9GYDuRmVlL9EGfdKBSUVI/9xAU7PCahA/1hbC2Jbmks9dlBBYjHF9IHMNY2jV/G2lIG7z0tJIW27Rog== - /babel-plugin-polyfill-corejs3/0.1.6_@babel+core@7.12.3: + integrity: sha512-DO95wD4g0A8KRaHKi0D51NdGXzvpqVLnLu5BTvDlpqUEpTmeEtypgC1xqesORaWmiUOQI14UHKlzNd9iZ2G3ZA== + /babel-plugin-polyfill-corejs3/0.1.7_@babel+core@7.12.3: dependencies: '@babel/core': 7.12.3 - '@babel/helper-define-polyfill-provider': 0.1.4_@babel+core@7.12.3 + '@babel/helper-define-polyfill-provider': 0.1.5_@babel+core@7.12.3 core-js-compat: 3.9.1 dev: true peerDependencies: '@babel/core': ^7.0.0-0 resolution: - integrity: sha512-IkYhCxPrjrUWigEmkMDXYzM5iblzKCdCD8cZrSAkQOyhhJm26DcG+Mxbx13QT//Olkpkg/AlRdT2L+Ww4Ciphw== - /babel-plugin-polyfill-regenerator/0.1.5_@babel+core@7.12.3: + integrity: sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw== + /babel-plugin-polyfill-regenerator/0.1.6_@babel+core@7.12.3: dependencies: '@babel/core': 7.12.3 - '@babel/helper-define-polyfill-provider': 0.1.4_@babel+core@7.12.3 + '@babel/helper-define-polyfill-provider': 0.1.5_@babel+core@7.12.3 dev: true peerDependencies: '@babel/core': ^7.0.0-0 resolution: - integrity: sha512-EyhBA6uN94W97lR7ecQVTvH9F5tIIdEw3ZqHuU4zekMlW82k5cXNXniiB7PRxQm06BqAjVr4sDT1mOy4RcphIA== + integrity: sha512-OUrYG9iKPKz8NxswXbRAdSwF0GhRdIEMTloQATJi4bDuFqrXaXcCUT/VGNrr8pBcjMh1RxZ7Xt9cytVJTJfvMg== /babel-plugin-syntax-object-rest-spread/6.13.0: dev: true resolution: @@ -3609,8 +3609,8 @@ integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== /browserslist/4.14.2: dependencies: - caniuse-lite: 1.0.30001192 - electron-to-chromium: 1.3.675 + caniuse-lite: 1.0.30001196 + electron-to-chromium: 1.3.682 escalade: 3.1.1 node-releases: 1.1.71 dev: true @@ -3621,9 +3621,9 @@ integrity: sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw== /browserslist/4.16.3: dependencies: - caniuse-lite: 1.0.30001192 + caniuse-lite: 1.0.30001196 colorette: 1.2.2 - electron-to-chromium: 1.3.675 + electron-to-chromium: 1.3.682 escalade: 3.1.1 node-releases: 1.1.71 dev: true @@ -3796,16 +3796,16 @@ /caniuse-api/3.0.0: dependencies: browserslist: 4.16.3 - caniuse-lite: 1.0.30001192 + caniuse-lite: 1.0.30001196 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true resolution: integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - /caniuse-lite/1.0.30001192: + /caniuse-lite/1.0.30001196: dev: true resolution: - integrity: sha512-63OrUnwJj5T1rUmoyqYTdRWBqFFxZFlyZnRRjDR8NSUQFB6A+j/uBORU/SyJ5WzDLg4SPiZH40hQCBNdZ/jmAw== + integrity: sha512-CPvObjD3ovWrNBaXlAIGWmg2gQQuJ5YhuciUOjPRox6hIQttu8O+b51dx6VIpIY9ESd2d0Vac1RKpICdG4rGUg== /capture-exit/2.0.0: dependencies: rsvp: 4.8.5 @@ -4056,17 +4056,17 @@ /color-name/1.1.4: resolution: integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - /color-string/1.5.4: + /color-string/1.5.5: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 dev: true resolution: - integrity: sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + integrity: sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== /color/3.1.3: dependencies: color-convert: 1.9.3 - color-string: 1.5.4 + color-string: 1.5.5 dev: true resolution: integrity: sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== @@ -4677,12 +4677,12 @@ node: '>=10' resolution: integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - /date-fns/2.17.0: + /date-fns/2.19.0: dev: false engines: node: '>=0.11' resolution: - integrity: sha512-ZEhqxUtEZeGgg9eHNSOAJ8O9xqSgiJdrL0lzSSfMF54x6KXWJiOH/xntSJ9YomJPrYH/p08t6gWjGWq1SDJlSA== + integrity: sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg== /dayjs/1.10.4: dev: false resolution: @@ -5039,10 +5039,10 @@ requiresBuild: true resolution: integrity: sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== - /electron-to-chromium/1.3.675: + /electron-to-chromium/1.3.682: dev: true resolution: - integrity: sha512-GEQw+6dNWjueXGkGfjgm7dAMtXfEqrfDG3uWcZdeaD4cZ3dKYdPRQVruVXQRXtPLtOr5GNVVlNLRMChOZ611pQ== + integrity: sha512-zok2y37qR00U14uM6qBz/3iIjWHom2eRfC2S1StA0RslP7x34jX+j4mxv80t8OEOHLJPVG54ZPeaFxEI7gPrwg== /elliptic/6.5.4: dependencies: bn.js: 4.12.0 @@ -5069,10 +5069,10 @@ dev: true resolution: integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - /emoji-regex/9.2.1: + /emoji-regex/9.2.2: dev: true resolution: - integrity: sha512-117l1H6U4X3Krn+MrzYrL57d5H7siRHWraBs7s+LjRuFK7Fe7hJqnJ0skWlinqsycVLU5YAo6L8CsEYQ0V5prg== + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== /emojis-list/2.1.0: dev: true engines: @@ -5142,24 +5142,7 @@ dev: true resolution: integrity: sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== - /es-abstract/1.17.7: - dependencies: - es-to-primitive: 1.2.1 - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.2 - is-callable: 1.2.3 - is-regex: 1.1.2 - object-inspect: 1.9.0 - object-keys: 1.1.1 - object.assign: 4.1.2 - string.prototype.trimend: 1.0.4 - string.prototype.trimstart: 1.0.4 - engines: - node: '>= 0.4' - resolution: - integrity: sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - /es-abstract/1.18.0-next.2: + /es-abstract/1.18.0: dependencies: call-bind: 1.0.2 es-to-primitive: 1.2.1 @@ -5170,16 +5153,17 @@ is-callable: 1.2.3 is-negative-zero: 2.0.1 is-regex: 1.1.2 + is-string: 1.0.5 object-inspect: 1.9.0 object-keys: 1.1.1 object.assign: 4.1.2 string.prototype.trimend: 1.0.4 string.prototype.trimstart: 1.0.4 - dev: true + unbox-primitive: 1.0.0 engines: node: '>= 0.4' resolution: - integrity: sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw== + integrity: sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== /es-to-primitive/1.2.1: dependencies: is-callable: 1.2.3 @@ -5247,10 +5231,10 @@ source-map: 0.6.1 resolution: integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - /eslint-config-react-app/6.0.0_4dcbf14bc95f25066c0a020f0b482e8c: + /eslint-config-react-app/6.0.0_6ff9c158aca78d020f9baefef3530e02: dependencies: - '@typescript-eslint/eslint-plugin': 4.15.2_82d802bc1360aa6114c0ff047240d01c - '@typescript-eslint/parser': 4.15.2_eslint@7.21.0+typescript@4.1.5 + '@typescript-eslint/eslint-plugin': 4.16.1_9c4985f94498b1423f0e374931685aad + '@typescript-eslint/parser': 4.16.1_eslint@7.21.0+typescript@4.1.5 babel-eslint: 10.1.0_eslint@7.21.0 confusing-browser-globals: 1.0.10 eslint: 7.21.0 @@ -5286,7 +5270,7 @@ /eslint-import-resolver-node/0.3.4: dependencies: debug: 2.6.9 - resolve: 1.20.0 + resolve: 1.18.1 dev: true resolution: integrity: sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== @@ -5325,7 +5309,7 @@ minimatch: 3.0.4 object.values: 1.1.3 read-pkg-up: 2.0.0 - resolve: 1.20.0 + resolve: 1.18.1 tsconfig-paths: 3.9.0 dev: true engines: @@ -5336,7 +5320,7 @@ integrity: sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== /eslint-plugin-jest/24.1.5_eslint@7.21.0+typescript@4.1.5: dependencies: - '@typescript-eslint/experimental-utils': 4.15.2_eslint@7.21.0+typescript@4.1.5 + '@typescript-eslint/experimental-utils': 4.16.1_eslint@7.21.0+typescript@4.1.5 eslint: 7.21.0 dev: true engines: @@ -5348,14 +5332,14 @@ integrity: sha512-FIP3lwC8EzEG+rOs1y96cOJmMVpdFNreoDJv29B5vIupVssRi8zrSY3QadogT0K3h1Y8TMxJ6ZSAzYUmFCp2hg== /eslint-plugin-jsx-a11y/6.4.1_eslint@7.21.0: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 aria-query: 4.2.2 array-includes: 3.1.3 ast-types-flow: 0.0.7 - axe-core: 4.1.2 + axe-core: 4.1.3 axobject-query: 2.2.0 damerau-levenshtein: 1.0.6 - emoji-regex: 9.2.1 + emoji-regex: 9.2.2 eslint: 7.21.0 has: 1.0.3 jsx-ast-utils: 3.2.0 @@ -5511,7 +5495,7 @@ strip-json-comments: 3.1.1 table: 6.0.7 text-table: 0.2.0 - v8-compile-cache: 2.2.0 + v8-compile-cache: 2.3.0 dev: true engines: node: ^10.12.0 || >=12.0.0 @@ -5979,7 +5963,7 @@ integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= /fork-ts-checker-webpack-plugin/4.1.6: dependencies: - '@babel/code-frame': 7.12.13 + '@babel/code-frame': 7.10.4 chalk: 2.4.2 micromatch: 3.1.10 minimatch: 3.0.4 @@ -6314,6 +6298,9 @@ dev: true resolution: integrity: sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA== + /has-bigints/1.0.1: + resolution: + integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== /has-flag/3.0.0: engines: node: '>=4' @@ -6399,7 +6386,7 @@ integrity: sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== /history/4.10.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.1.0 @@ -6610,7 +6597,7 @@ integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== /i18next-browser-languagedetector/6.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 dev: false resolution: integrity: sha512-3H+OsNQn3FciomUU0d4zPFHsvJv4X66lBelXk9hnIDYDsveIgT7dWZ3/VvcSlpKk9lvCK770blRZ/CwHMXZqWw== @@ -6620,12 +6607,12 @@ dev: false resolution: integrity: sha512-li3Un71yBu1uCfgJrCg8DOEtljse1er0ja1pM/rFmWLihrD2RFqyrglOsYUHokvzZ53mJB2Mmu4DYgd8/t3R0A== - /i18next/19.9.0: + /i18next/19.9.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 dev: false resolution: - integrity: sha512-5zRG3aFl+e+LsdpVUp0dKkVhYH2iCv+gxyzXP1q2oJUc3BV26fqX87cBE3AHkMOir1X0liOaSoxS/Kg95iEcEQ== + integrity: sha512-9Azzyb3DvMJUMd7sPhwVEs6PQcogvdHmLQTjMQ+P+h3XwW4O66/8lgZTmYShgkjPOCqTw4Fwl5LOp/VhZgPo5A== /iconv-lite/0.4.24: dependencies: safer-buffer: 2.1.2 @@ -6860,6 +6847,9 @@ dev: true resolution: integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + /is-bigint/1.0.1: + resolution: + integrity: sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== /is-binary-path/1.0.1: dependencies: binary-extensions: 1.13.1 @@ -6877,6 +6867,13 @@ optional: true resolution: integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + /is-boolean-object/1.1.0: + dependencies: + call-bind: 1.0.2 + engines: + node: '>= 0.4' + resolution: + integrity: sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== /is-buffer/1.1.6: dev: true resolution: @@ -7023,11 +7020,15 @@ resolution: integrity: sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= /is-negative-zero/2.0.1: - dev: true engines: node: '>= 0.4' resolution: integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + /is-number-object/1.0.4: + engines: + node: '>= 0.4' + resolution: + integrity: sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== /is-number/3.0.0: dependencies: kind-of: 3.2.2 @@ -7131,7 +7132,6 @@ resolution: integrity: sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== /is-string/1.0.5: - dev: true engines: node: '>= 0.4' resolution: @@ -7548,7 +7548,7 @@ jest-pnp-resolver: 1.2.2_jest-resolve@26.6.0 jest-util: 26.6.2 read-pkg-up: 7.0.1 - resolve: 1.20.0 + resolve: 1.18.1 slash: 3.0.0 dev: true engines: @@ -7563,7 +7563,7 @@ jest-pnp-resolver: 1.2.2_jest-resolve@26.6.2 jest-util: 26.6.2 read-pkg-up: 7.0.1 - resolve: 1.20.0 + resolve: 1.18.1 slash: 3.0.0 dev: true engines: @@ -7646,7 +7646,7 @@ '@babel/types': 7.13.0 '@jest/types': 26.6.2 '@types/babel__traverse': 7.11.0 - '@types/prettier': 2.2.1 + '@types/prettier': 2.2.2 chalk: 4.1.0 expect: 26.6.2 graceful-fs: 4.2.6 @@ -8367,7 +8367,7 @@ integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== /mini-create-react-context/0.4.1_prop-types@15.7.2+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 prop-types: 15.7.2 react: 17.0.1 tiny-warning: 1.0.3 @@ -8683,7 +8683,7 @@ /normalize-package-data/2.5.0: dependencies: hosted-git-info: 2.8.8 - resolve: 1.20.0 + resolve: 1.18.1 semver: 5.7.1 validate-npm-package-license: 3.0.4 dev: true @@ -8814,7 +8814,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 has: 1.0.3 dev: true engines: @@ -8825,7 +8825,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 has: 1.0.3 dev: true engines: @@ -8836,7 +8836,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 dev: true engines: node: '>= 0.8' @@ -8854,7 +8854,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 has: 1.0.3 dev: true engines: @@ -9854,7 +9854,7 @@ dependencies: autoprefixer: 9.8.6 browserslist: 4.16.3 - caniuse-lite: 1.0.30001192 + caniuse-lite: 1.0.30001196 css-blank-pseudo: 0.1.4 css-has-pseudo: 0.10.0 css-prefers-color-scheme: 3.1.1 @@ -9933,7 +9933,7 @@ integrity: sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== /postcss-safe-parser/5.0.2: dependencies: - postcss: 8.2.6 + postcss: 8.2.7 dev: true engines: node: '>=10.0' @@ -10043,7 +10043,7 @@ node: '>=6.0.0' resolution: integrity: sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== - /postcss/8.2.6: + /postcss/8.2.7: dependencies: colorette: 1.2.2 nanoid: 3.1.20 @@ -10052,7 +10052,7 @@ engines: node: ^10 || ^12 || >=14 resolution: - integrity: sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg== + integrity: sha512-DsVLH3xJzut+VT+rYr0mtvOtpTjSyqDwPf5EZWXcb0uAKfitGpTY9Ec+afi2+TgdN8rWS9Cs88UDYehKo/RvOw== /prelude-ls/1.1.2: dev: true engines: @@ -10129,7 +10129,7 @@ /promise.prototype.finally/3.1.2: dependencies: define-properties: 1.1.3 - es-abstract: 1.17.7 + es-abstract: 1.18.0 function-bind: 1.1.1 dev: false engines: @@ -10320,7 +10320,7 @@ integrity: sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== /rc-align/4.0.9_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 dom-align: 1.12.0 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10335,7 +10335,7 @@ integrity: sha512-myAM2R4qoB6LqBul0leaqY8gFaiECDJ3MtQDmzDo9xM9NRT/04TvWOYd2YHU9zvGzqk9QXF6S9/MifzSKDZeMw== /rc-cascader/1.4.2_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 array-tree-filter: 2.1.0 rc-trigger: 5.2.3_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10350,7 +10350,7 @@ integrity: sha512-JVuLGrSi+3G8DZyPvlKlGVWJjhoi9NTz6REHIgRspa5WnznRkKGm2ejb0jJtz0m2IL8Q9BG4ZA2sXuqAu71ltQ== /rc-checkbox/2.3.2_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -10362,7 +10362,7 @@ integrity: sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg== /rc-collapse/3.1.0_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10375,9 +10375,9 @@ react-dom: '>=16.9.0' resolution: integrity: sha512-EwpNPJcLe7b+5JfyaxM9ZNnkCgqArt3QQO0Cr5p5plwz/C9h8liAmjYY5I4+hl9lAjBqb7ZwLu94+z+rt5g1WQ== - /rc-dialog/8.5.1_react-dom@17.0.1+react@17.0.1: + /rc-dialog/8.5.2_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10388,10 +10388,10 @@ react: '>=16.9.0' react-dom: '>=16.9.0' resolution: - integrity: sha512-EcLgHHjF3Jp4C+TFceO2j7gIrpx0YIhY6ronki5QJDL/z+qWYozY5RNh4rnv4a6R21SPVhV+SK+gMMlMHZ/YRQ== + integrity: sha512-3n4taFcjqhTE9uNuzjB+nPDeqgRBTEGBfe46mb1e7r88DgDo0lL4NnxY/PZ6PJKd2tsCt+RrgF/+YeTvJ/Thsw== /rc-drawer/4.3.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10404,7 +10404,7 @@ integrity: sha512-GMfFy4maqxS9faYXEhQ+0cA1xtkddEQzraf6SAdzWbn444DrrLogwYPk1NXSpdXjLCLxgxOj9MYtyYG42JsfXg== /rc-dropdown/3.2.0_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-trigger: 5.2.3_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10417,7 +10417,7 @@ integrity: sha512-j1HSw+/QqlhxyTEF6BArVZnTmezw2LnSmRk6I9W7BCqNCKaRwleRmMMs1PHbuaG8dKHVqP6e21RQ7vPBLVnnNw== /rc-field-form/1.19.0_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 async-validator: 3.5.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10432,9 +10432,9 @@ integrity: sha512-FZFyE6FmhR4xGO3MVo86J2sHIM6t/W4UqN94nVqZC/9PLhzSaKuM0i9UfwFMtuENP2hxFANEQZMBrD/2Zd43pg== /rc-image/5.2.3_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 - rc-dialog: 8.5.1_react-dom@17.0.1+react@17.0.1 + rc-dialog: 8.5.2_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -10444,9 +10444,9 @@ react-dom: '>=16.9.0' resolution: integrity: sha512-8qWNerW1rN0s4zAF6oEa+Zm7UzM+PwTxbGdufvnR3Gcp2M0bcfoEPk9V+RgTxmzGNNELxmrMHloPL4LV5BZu3Q== - /rc-input-number/7.0.0_react-dom@17.0.1+react@17.0.1: + /rc-input-number/7.0.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10456,10 +10456,10 @@ react: '>=16.9.0' react-dom: '>=16.9.0' resolution: - integrity: sha512-79ZAKz1OE7RTlfrSOhe/jAifFoMsKQ/u9M8qw5PLzEO2FGthtFcxoQTCTs5WPjs9tElG5HPG/YCP5Ugb3XRGEA== + integrity: sha512-LOPEwhEjkJzDd+0mOMNZdRKjEsZJOXVxmNr3sfiONu9PUCRf366GDlARh+AcSd/jWvFzEa070wpr2gg0i/5BfQ== /rc-mentions/1.5.3_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-menu: 8.10.6_react-dom@17.0.1+react@17.0.1 rc-textarea: 0.3.4_react-dom@17.0.1+react@17.0.1 @@ -10475,7 +10475,7 @@ integrity: sha512-NG/KB8YiKBCJPHHvr/QapAb4f9YzLJn7kDHtmI1K6t7ZMM5YgrjIxNNhoRKKP9zJvb9PdPts69Hbg4ZMvLVIFQ== /rc-menu/8.10.6_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 mini-store: 3.0.6_react-dom@17.0.1+react@17.0.1 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 @@ -10493,7 +10493,7 @@ integrity: sha512-RVkd8XChwSmVOdNULbqLNnABthRZWnhqct1Q74onEXTClsXvsLADMhlIJtw/umglVSECM+14TJdIli9rl2Bzlw== /rc-motion/2.4.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10504,9 +10504,9 @@ react-dom: '>=16.9.0' resolution: integrity: sha512-TWLvymfMu8SngPx5MDH8dQ0D2RYbluNTfam4hY/dNNx9RQ3WtGuZ/GXHi2ymLMzH+UNd6EEFYkOuR5JTTtm8Xg== - /rc-notification/4.5.4_react-dom@17.0.1+react@17.0.1: + /rc-notification/4.5.5_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10519,10 +10519,10 @@ react: '>=16.9.0' react-dom: '>=16.9.0' resolution: - integrity: sha512-VsN0ouF4uglE5g3C9oDsXLNYX0Sz++ZNUFYCswkxhpImYJ9u6nJOpyA71uOYDVCu6bAF54Y5Hi/b+EcnMzkepg== + integrity: sha512-YIfhTSw+h5GsSdgMnuMx24wqiPlg3FeamuOlkh9RkyHx+SeZVAKzQ0juy2NGvPEF2hDWi5xTqxUqLdo0L2AmGg== /rc-overflow/1.0.2_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-resize-observer: 1.0.0_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10534,9 +10534,9 @@ react-dom: '>=16.9.0' resolution: integrity: sha512-GXj4DAyNxm4f57LvXLwhJaZoJHzSge2l2lQq64MZP7NJAfLpQqOLD+v9JMV9ONTvDPZe8kdzR+UMmkAn7qlzFA== - /rc-pagination/3.1.3_react-dom@17.0.1+react@17.0.1: + /rc-pagination/3.1.4_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -10545,12 +10545,12 @@ react: '>=16.9.0' react-dom: '>=16.9.0' resolution: - integrity: sha512-Z7CdC4xGkedfAwcUHPtfqNhYwVyDgkmhkvfsmoByCOwAd89p42t5O5T3ORar1wRmVWf3jxk/Bf4k0atenNvlFA== + integrity: sha512-6nFsNXGfBb6Hh3SNeTHuEaAWiq33PdDA4rdi2Ba2LIri6XUeOCmWLFPUwZyx+NJn+ODGCxsl5BEVcfLbNsxtGg== /rc-picker/2.5.7_89622fd8e4ec221151a62783d49305af: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 - date-fns: 2.17.0 + date-fns: 2.19.0 dayjs: 1.10.4 moment: 2.29.1 rc-trigger: 5.2.3_react-dom@17.0.1+react@17.0.1 @@ -10569,7 +10569,7 @@ integrity: sha512-b5ZWCKds1u4H9fP0EpJ8YUX16VhueK6E7TxgK+rRjpbKwI1bOUy59F/OkUXjdACJdDmbsDMDZogpW7KzoqtzvA== /rc-progress/3.1.3_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -10581,7 +10581,7 @@ integrity: sha512-Jl4fzbBExHYMoC6HBPzel0a9VmhcSXx24LVt/mdhDM90MuzoMCJjXZAlhA0V0CJi+SKjMhfBoIQ6Lla1nD4QNw== /rc-rate/2.9.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10596,7 +10596,7 @@ integrity: sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA== /rc-resize-observer/1.0.0_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10608,9 +10608,9 @@ react-dom: '>=16.9.0' resolution: integrity: sha512-RgKGukg1mlzyGdvzF7o/LGFC8AeoMH9aGzXTUdp6m+OApvmRdUuOscq/Y2O45cJA+rXt1ApWlpFoOIioXL3AGg== - /rc-select/12.1.3_react-dom@17.0.1+react@17.0.1: + /rc-select/12.1.5_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 rc-overflow: 1.0.2_react-dom@17.0.1+react@17.0.1 @@ -10626,12 +10626,12 @@ react: '*' react-dom: '*' resolution: - integrity: sha512-pMJ27VQRh5QbyGLSE+by4tORYucNFbZxON+Ywj81qjXAGMjvhMcOOvlv1RZRNdnZxaMwH//3iDPOf80b0AJxZg== + integrity: sha512-UElTMw0+XvYJmVfsHTWvLR42RKNf5qyN3Ed/JfuZQceIPK1/3ugGRjdEOKBsPmPyNB5389NAROCV4tQd9fmqwg== /rc-slider/9.7.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 - rc-tooltip: 5.0.2_react-dom@17.0.1+react@17.0.1 + rc-tooltip: 5.1.0_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -10646,7 +10646,7 @@ integrity: sha512-r9r0dpFA3PEvxBhIfVi1lVzxuSogWxeY+tGvi2AqMM1rPgaOXQ7WbtT+9kVFkJ9K8TntA/vYPgiCCKfN29KTkw== /rc-steps/4.1.3_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10661,7 +10661,7 @@ integrity: sha512-GXrMfWQOhN3sVze3JnzNboHpQdNHcdFubOETUHyDpa/U3HEKBZC3xJ8XK4paBgF4OJ3bdUVLC+uBPc6dCxvDYA== /rc-switch/3.2.2_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10674,7 +10674,7 @@ integrity: sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A== /rc-table/7.13.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-resize-observer: 1.0.0_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10691,7 +10691,7 @@ integrity: sha512-zg2ldSRHj1ENGsSykSKV5axnWkSaaly+wjRcD1Bspx4WHrf3m/I1WYjpVvOeer2e06bfKb6lmkK0HLxQ1cZtsg== /rc-tabs/11.7.3_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-dropdown: 3.2.0_react-dom@17.0.1+react@17.0.1 rc-menu: 8.10.6_react-dom@17.0.1+react@17.0.1 @@ -10709,7 +10709,7 @@ integrity: sha512-5nd2NVss9TprPRV9r8N05SjQyAE7zDrLejxFLcbJ+BdLxSwnGnk3ws/Iq0smqKZUnPQC0XEvnpF3+zlllUUT2w== /rc-textarea/0.3.4_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-resize-observer: 1.0.0_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10721,9 +10721,9 @@ react-dom: '>=16.9.0' resolution: integrity: sha512-ILUYx831ZukQPv3m7R4RGRtVVWmL1LV4ME03L22mvT56US0DGCJJaRTHs4vmpcSjFHItph5OTmhodY4BOwy81A== - /rc-tooltip/5.0.2_react-dom@17.0.1+react@17.0.1: + /rc-tooltip/5.1.0_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 rc-trigger: 5.2.3_react-dom@17.0.1+react@17.0.1 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -10732,13 +10732,13 @@ react: '>=16.9.0' react-dom: '>=16.9.0' resolution: - integrity: sha512-A4FejSG56PzYtSNUU4H1pVzfhtkV/+qMT2clK0CsSj+9mbc4USEtpWeX6A/jjVL+goBOMKj8qlH7BCZmZWh/Nw== + integrity: sha512-pFqD1JZwNIpbdcefB7k5xREoHAWM/k3yQwYF0iminbmDXERgq4rvBfUwIvlCqqZSM7HDr9hYeYr6ZsVNaKtvCQ== /rc-tree-select/4.3.0_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 - rc-select: 12.1.3_react-dom@17.0.1+react@17.0.1 - rc-tree: 4.1.2_react-dom@17.0.1+react@17.0.1 + rc-select: 12.1.5_react-dom@17.0.1+react@17.0.1 + rc-tree: 4.1.3_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 @@ -10748,9 +10748,9 @@ react-dom: '*' resolution: integrity: sha512-EEXB9dKBsJNJuKIU5NERZsaJ71GDGIj5uWLl7A4XiYr2jXM4JICfScvvp3O5jHMDfhqmgpqNc0z90mHkgh3hKg== - /rc-tree/4.1.2_react-dom@17.0.1+react@17.0.1: + /rc-tree/4.1.3_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 @@ -10764,10 +10764,10 @@ react: '*' react-dom: '*' resolution: - integrity: sha512-9yhhDqHxG8gOZfkZeHYT6oarzarzi37lDe5c2r72tq5dflvoayGqD2bMkL2KC7GQJPLknZrtCwAbewqvD/T6NQ== + integrity: sha512-eCp5KwzDOHRdub4hM1islJ8kcX8LrtWJxNRiGRk21dCyXW6XYHeOyzRzNekwlCrn8bdEahsze1hUPT9s104d5g== /rc-trigger/5.2.3_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-align: 4.0.9_react-dom@17.0.1+react@17.0.1 rc-motion: 2.4.1_react-dom@17.0.1+react@17.0.1 @@ -10782,9 +10782,9 @@ react-dom: '>=16.9.0' resolution: integrity: sha512-6Fokao07HUbqKIDkDRFEM0AGZvsvK0Fbp8A/KFgl1ngaqfO1nY037cISCG1Jm5fxImVsXp9awdkP7Vu5cxjjog== - /rc-upload/4.0.0-alpha.6_react-dom@17.0.1+react@17.0.1: + /rc-upload/4.0.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 classnames: 2.2.6 rc-util: 5.8.1_react-dom@17.0.1+react@17.0.1 react: 17.0.1 @@ -10794,10 +10794,10 @@ react: '>=16.9.0' react-dom: '>=16.9.0' resolution: - integrity: sha512-LgcHy0PL5HyVmOEhOXjYzljpXgfFTFmTlraLv4j0Gy9F2owNNt/Mkiu2NJTj+RVZoi7QnxzZeGWoRa1rVdPV4Q== + integrity: sha512-h7iLt22U0C2ObpqTU7MDoAV/FDwHTUEFjMd6uidA4ipoJ7gP7C74gOQOtl5OPQTVUDATUndnSAt9uyC1znlo9A== /rc-util/5.8.1_react-dom@17.0.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 react-is: 16.13.1 @@ -10905,11 +10905,11 @@ react: '>= 0.14.0' resolution: integrity: sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q== - /react-i18next/11.8.8_i18next@19.9.0+react@17.0.1: + /react-i18next/11.8.8_i18next@19.9.1+react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 html-parse-stringify2: 2.0.1 - i18next: 19.9.0 + i18next: 19.9.1 react: 17.0.1 dev: false peerDependencies: @@ -10932,7 +10932,7 @@ integrity: sha512-7gcIUvJkAXXttt1fmBK9cwn+1jTa4hbKLGCZ9J1U6EOkyb2/+LKL1Z28d9rtDLMnpvImlNlLPdTPooorl5cpmg== /react-redux/7.2.2_380dc38591053d98779d1f25fc7202b9: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 prop-types: 15.7.2 @@ -10967,7 +10967,7 @@ integrity: sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== /react-router-dom/5.2.0_react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.7.2 @@ -10982,7 +10982,7 @@ integrity: sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== /react-router/5.2.0_react@17.0.1: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 history: 4.10.1 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 @@ -11003,8 +11003,8 @@ '@babel/core': 7.12.3 '@pmmmwh/react-refresh-webpack-plugin': 0.4.3_9f0995138d24e525eb86c097d82409c0 '@svgr/webpack': 5.5.0 - '@typescript-eslint/eslint-plugin': 4.15.2_82d802bc1360aa6114c0ff047240d01c - '@typescript-eslint/parser': 4.15.2_eslint@7.21.0+typescript@4.1.5 + '@typescript-eslint/eslint-plugin': 4.16.1_9c4985f94498b1423f0e374931685aad + '@typescript-eslint/parser': 4.16.1_eslint@7.21.0+typescript@4.1.5 babel-eslint: 10.1.0_eslint@7.21.0 babel-jest: 26.6.3_@babel+core@7.12.3 babel-loader: 8.1.0_427212bc1158d185e577033f19ca0757 @@ -11017,7 +11017,7 @@ dotenv: 8.2.0 dotenv-expand: 5.1.0 eslint: 7.21.0 - eslint-config-react-app: 6.0.0_4dcbf14bc95f25066c0a020f0b482e8c + eslint-config-react-app: 6.0.0_6ff9c158aca78d020f9baefef3530e02 eslint-plugin-flowtype: 5.3.1_eslint@7.21.0 eslint-plugin-import: 2.22.1_eslint@7.21.0 eslint-plugin-jest: 24.1.5_eslint@7.21.0+typescript@4.1.5 @@ -11234,7 +11234,7 @@ integrity: sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== /regenerator-transform/0.14.5: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 dev: true resolution: integrity: sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== @@ -12269,7 +12269,7 @@ dependencies: call-bind: 1.0.2 define-properties: 1.1.3 - es-abstract: 1.18.0-next.2 + es-abstract: 1.18.0 has-symbols: 1.0.2 internal-slot: 1.0.3 regexp.prototype.flags: 1.3.1 @@ -12883,6 +12883,14 @@ dev: false resolution: integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + /unbox-primitive/1.0.0: + dependencies: + function-bind: 1.1.1 + has-bigints: 1.0.1 + has-symbols: 1.0.2 + which-boxed-primitive: 1.0.2 + resolution: + integrity: sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA== /unicode-canonical-property-names-ecmascript/1.0.4: dev: true engines: @@ -13049,7 +13057,7 @@ /util.promisify/1.0.1: dependencies: define-properties: 1.1.3 - es-abstract: 1.17.7 + es-abstract: 1.18.0 has-symbols: 1.0.2 object.getownpropertydescriptors: 2.1.2 dev: true @@ -13092,10 +13100,10 @@ hasBin: true resolution: integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - /v8-compile-cache/2.2.0: + /v8-compile-cache/2.3.0: dev: true resolution: - integrity: sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== /v8-to-istanbul/7.1.0: dependencies: '@types/istanbul-lib-coverage': 2.0.3 @@ -13435,6 +13443,15 @@ node: '>=10' resolution: integrity: sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw== + /which-boxed-primitive/1.0.2: + dependencies: + is-bigint: 1.0.1 + is-boolean-object: 1.1.0 + is-number-object: 1.0.4 + is-string: 1.0.5 + is-symbol: 1.0.3 + resolution: + integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== /which-module/2.0.0: dev: true resolution: @@ -13476,8 +13493,8 @@ /workbox-build/5.1.4: dependencies: '@babel/core': 7.12.3 - '@babel/preset-env': 7.13.8_@babel+core@7.12.3 - '@babel/runtime': 7.13.8 + '@babel/preset-env': 7.13.9_@babel+core@7.12.3 + '@babel/runtime': 7.13.9 '@hapi/joi': 15.1.1 '@rollup/plugin-node-resolve': 7.1.3_rollup@1.32.1 '@rollup/plugin-replace': 2.4.1_rollup@1.32.1 @@ -13609,7 +13626,7 @@ integrity: sha512-9xKnKw95aXwSNc8kk8gki4HU0g0W6KXu+xks7wFuC7h0sembFnTrKtckqZxbSod41TDaGh+gWUA5IRXrL0ECRA== /workbox-webpack-plugin/5.1.4_webpack@4.44.2: dependencies: - '@babel/runtime': 7.13.8 + '@babel/runtime': 7.13.9 fast-json-stable-stringify: 2.1.0 source-map-url: 0.4.1 upath: 1.2.0 @@ -13799,7 +13816,7 @@ '@craco/craco': ^6.1.1 '@testing-library/jest-dom': ^5.11.9 '@testing-library/react': ^11.2.5 - '@testing-library/user-event': ^12.8.0 + '@testing-library/user-event': ^12.8.1 '@types/classnames': ^2.2.11 '@types/debug': ^4.1.5 '@types/file-saver': ^2.0.1 @@ -13818,7 +13835,7 @@ '@types/webpack-env': ^1.16.0 '@typescript-eslint/eslint-plugin': 4.15.3-alpha.17 '@typescript-eslint/parser': 4.15.3-alpha.17 - antd: ^4.13.0 + antd: ^4.13.1 antd-dayjs-webpack-plugin: ^1.0.6 async-mutex: ^0.3.1 await-to-js: ^2.1.1 @@ -13836,7 +13853,7 @@ eslint-plugin-react-hooks: ^4.2.0 eslint-plugin-tsdoc: ^0.2.11 file-saver: ^2.0.5 - i18next: ^19.9.0 + i18next: ^19.9.1 i18next-browser-languagedetector: ^6.0.1 i18next-http-backend: ^1.1.1 lodash-es: ^4.17.21 diff --git a/src/components/addresses/ContextualAddress.tsx b/src/components/addresses/ContextualAddress.tsx index 4f64676..3849723 100644 --- a/src/components/addresses/ContextualAddress.tsx +++ b/src/components/addresses/ContextualAddress.tsx @@ -11,7 +11,7 @@ import { Link } from "react-router-dom"; import { KristAddress } from "@api/types"; -import { Wallet, useWallets } from "@wallets/Wallet"; +import { Wallet, useWallets } from "@wallets"; import { parseCommonMeta, CommonMeta } from "@utils/commonmeta"; import { stripNameSuffix } from "@utils/currency"; import { useBooleanSetting } from "@utils/settings"; diff --git a/src/components/auth/AuthMasterPasswordPopover.tsx b/src/components/auth/AuthMasterPasswordPopover.tsx index fd64868..fad2f6c 100644 --- a/src/components/auth/AuthMasterPasswordPopover.tsx +++ b/src/components/auth/AuthMasterPasswordPopover.tsx @@ -13,7 +13,7 @@ import { FakeUsernameInput } from "./FakeUsernameInput"; import { getMasterPasswordInput } from "./MasterPasswordInput"; -import { authMasterPassword } from "@wallets/WalletManager"; +import { authMasterPassword } from "@wallets"; interface FormValues { masterPassword: string; @@ -26,7 +26,7 @@ } export const AuthMasterPasswordPopover: FC = ({ encrypt, onSubmit, placement, children }) => { - const { salt, tester } = useSelector((s: RootState) => s.walletManager, shallowEqual); + const { salt, tester } = useSelector((s: RootState) => s.masterPassword, shallowEqual); const { t } = useTranslation(); const [form] = Form.useForm(); diff --git a/src/components/auth/AuthorisedAction.tsx b/src/components/auth/AuthorisedAction.tsx index f73bfbf..749fcde 100644 --- a/src/components/auth/AuthorisedAction.tsx +++ b/src/components/auth/AuthorisedAction.tsx @@ -20,7 +20,7 @@ export const AuthorisedAction: FC = ({ encrypt, onAuthed, popoverPlacement, children }) => { const { isAuthed, hasMasterPassword } - = useSelector((s: RootState) => s.walletManager, shallowEqual); + = useSelector((s: RootState) => s.masterPassword, shallowEqual); // Don't render the modal and popover unless we absolutely have to const [clicked, setClicked] = useState(false); diff --git a/src/components/auth/ForcedAuth.tsx b/src/components/auth/ForcedAuth.tsx index 0a21e5d..645f05f 100644 --- a/src/components/auth/ForcedAuth.tsx +++ b/src/components/auth/ForcedAuth.tsx @@ -7,7 +7,7 @@ import { useSelector, shallowEqual } from "react-redux"; import { RootState } from "@store"; -import { authMasterPassword } from "@wallets/WalletManager"; +import { authMasterPassword } from "@wallets"; import { useMountEffect } from "@utils"; @@ -27,7 +27,7 @@ * containing the master password, and automatically authenticate with it. */ export function ForcedAuth(): JSX.Element | null { const { isAuthed, hasMasterPassword, salt, tester } - = useSelector((s: RootState) => s.walletManager, shallowEqual); + = useSelector((s: RootState) => s.masterPassword, shallowEqual); const { t } = useTranslation(); diff --git a/src/components/auth/SetMasterPasswordModal.tsx b/src/components/auth/SetMasterPasswordModal.tsx index 8b883d7..ac0b45d 100644 --- a/src/components/auth/SetMasterPasswordModal.tsx +++ b/src/components/auth/SetMasterPasswordModal.tsx @@ -8,7 +8,7 @@ import { FakeUsernameInput } from "./FakeUsernameInput"; import { getMasterPasswordInput } from "./MasterPasswordInput"; -import { setMasterPassword } from "@wallets/WalletManager"; +import { setMasterPassword } from "@wallets"; interface Props { visible: boolean; diff --git a/src/components/results/APIErrorResult.tsx b/src/components/results/APIErrorResult.tsx index 56bedf6..c0e3cec 100644 --- a/src/components/results/APIErrorResult.tsx +++ b/src/components/results/APIErrorResult.tsx @@ -48,8 +48,10 @@ return } - title={props.invalidParameterTitleKey} - subTitle={props.invalidParameterSubTitleKey} + title={t(props.invalidParameterTitleKey)} + subTitle={props.invalidParameterSubTitleKey + ? t(props.invalidParameterSubTitleKey) + : undefined} />; } @@ -58,8 +60,10 @@ return } - title={props.notFoundTitleKey} - subTitle={props.notFoundSubTitleKey} + title={t(props.notFoundTitleKey)} + subTitle={props.notFoundSubTitleKey + ? t(props.notFoundSubTitleKey) + : undefined} />; } } diff --git a/src/components/transactions/TransactionItem.tsx b/src/components/transactions/TransactionItem.tsx index 8c5563f..8c8eeff 100644 --- a/src/components/transactions/TransactionItem.tsx +++ b/src/components/transactions/TransactionItem.tsx @@ -9,7 +9,7 @@ import { Link } from "react-router-dom"; import { KristTransaction } from "@api/types"; -import { Wallet } from "@wallets/Wallet"; +import { Wallet } from "@wallets"; import { DateTime } from "../DateTime"; import { KristValue } from "../krist/KristValue"; import { KristNameLink } from "../names/KristNameLink"; diff --git a/src/components/transactions/TransactionSummary.tsx b/src/components/transactions/TransactionSummary.tsx index 846639f..cd6288a 100644 --- a/src/components/transactions/TransactionSummary.tsx +++ b/src/components/transactions/TransactionSummary.tsx @@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; import { KristTransaction } from "@api/types"; import { TransactionItem } from "./TransactionItem"; diff --git a/src/components/transactions/TransactionType.tsx b/src/components/transactions/TransactionType.tsx index 6e97299..22bf4c5 100644 --- a/src/components/transactions/TransactionType.tsx +++ b/src/components/transactions/TransactionType.tsx @@ -8,7 +8,7 @@ import { Link } from "react-router-dom"; import { KristTransaction, KristTransactionType } from "@api/types"; -import { Wallet, useWallets } from "@wallets/Wallet"; +import { Wallet, useWallets } from "@wallets"; import "./TransactionType.less"; diff --git a/src/components/wallets/SelectWalletCategory.tsx b/src/components/wallets/SelectWalletCategory.tsx index fa06c1a..ef68a33 100644 --- a/src/components/wallets/SelectWalletCategory.tsx +++ b/src/components/wallets/SelectWalletCategory.tsx @@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"; import { localeSort } from "@utils"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; const { Text } = Typography; diff --git a/src/components/wallets/SelectWalletFormat.tsx b/src/components/wallets/SelectWalletFormat.tsx index ed10057..4439425 100644 --- a/src/components/wallets/SelectWalletFormat.tsx +++ b/src/components/wallets/SelectWalletFormat.tsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; -import { WalletFormatName, ADVANCED_FORMATS } from "@wallets/WalletFormat"; +import { WalletFormatName, ADVANCED_FORMATS } from "@wallets"; import { useBooleanSetting } from "@utils/settings"; interface Props { diff --git a/src/components/wallets/SyncWallets.tsx b/src/components/wallets/SyncWallets.tsx index a91a12a..86ed69d 100644 --- a/src/components/wallets/SyncWallets.tsx +++ b/src/components/wallets/SyncWallets.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { syncWallets } from "@wallets/Wallet"; +import { syncWallets } from "@wallets"; import { useMountEffect } from "@utils"; /** Sync the wallets with the Krist node on startup. */ diff --git a/src/global/ws/SyncMOTD.tsx b/src/global/ws/SyncMOTD.tsx index 5ce3826..8331231 100644 --- a/src/global/ws/SyncMOTD.tsx +++ b/src/global/ws/SyncMOTD.tsx @@ -12,7 +12,7 @@ import * as api from "@api"; import { KristMOTD, KristMOTDBase } from "@api/types"; -import { recalculateWallets, useWallets } from "@wallets/Wallet"; +import { recalculateWallets, useWallets } from "@wallets"; import Debug from "debug"; const debug = Debug("kristweb:sync-motd"); @@ -46,7 +46,7 @@ // All these are used to determine if we need to recalculate the addresses const addressPrefix = useSelector((s: RootState) => s.node.currency.address_prefix); - const masterPassword = useSelector((s: RootState) => s.walletManager.masterPassword); + const masterPassword = useSelector((s: RootState) => s.masterPassword.masterPassword); const { wallets } = useWallets(); // Update the MOTD when the sync node changes, and on startup diff --git a/src/global/ws/WebsocketService.tsx b/src/global/ws/WebsocketService.tsx index e25cc2c..be4050c 100644 --- a/src/global/ws/WebsocketService.tsx +++ b/src/global/ws/WebsocketService.tsx @@ -4,13 +4,12 @@ import { useState, useEffect } from "react"; import { store } from "@app"; -import { WalletMap } from "@reducers/WalletsReducer"; import * as wsActions from "@actions/WebsocketActions"; import * as nodeActions from "@actions/NodeActions"; import * as api from "@api"; import { KristAddress, KristBlock, KristTransaction, WSConnectionState, WSIncomingMessage, WSSubscriptionLevel } from "@api/types"; -import { useWallets, findWalletByAddress, syncWallet, syncWalletUpdate, Wallet } from "@wallets/Wallet"; +import { Wallet, WalletMap, useWallets, findWalletByAddress, syncWallet, syncWalletUpdate } from "@wallets"; import WebSocketAsPromised from "websocket-as-promised"; import { throttle } from "lodash-es"; diff --git a/src/index.tsx b/src/index.tsx index 845e5f1..3a17c07 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,6 +17,10 @@ debug("============================ APP STARTING ============================"); debug("performing initial render"); ReactDOM.render( + // FIXME: ant-design still has a few incompatibilities with StrictMode, most + // notably in rc-menu. Keep an eye on the issue to track progress and + // prepare for React 17: + // https://github.com/ant-design/ant-design/issues/26136 // , // , diff --git a/src/krist/AddressAlgo.ts b/src/krist/AddressAlgo.ts deleted file mode 100644 index 19b539a..0000000 --- a/src/krist/AddressAlgo.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { sha256, doubleSHA256 } from "@utils/crypto"; - -const hexToBase36 = (input: number): string => { - const byte = 48 + Math.floor(input / 7); - return String.fromCharCode(byte + 39 > 122 ? 101 : byte > 57 ? byte + 39 : byte); -}; - -export const makeV2Address = async (addressPrefix: string, key: string): Promise => { - const chars = ["", "", "", "", "", "", "", "", ""]; - let chain = addressPrefix; - let hash = await doubleSHA256(key); - - for (let i = 0; i <= 8; i++) { - chars[i] = hash.substring(0, 2); - hash = await doubleSHA256(hash); - } - - for (let i = 0; i <= 8;) { - const index = parseInt(hash.substring(2 * i, 2 + (2 * i)), 16) % 9; - - if (chars[index] === "") { - hash = await sha256(hash); - } else { - chain += hexToBase36(parseInt(chars[index], 16)); - chars[index] = ""; - i++; - } - } - - return chain; -}; diff --git a/src/krist/addressAlgo.ts b/src/krist/addressAlgo.ts new file mode 100644 index 0000000..19b539a --- /dev/null +++ b/src/krist/addressAlgo.ts @@ -0,0 +1,34 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { sha256, doubleSHA256 } from "@utils/crypto"; + +const hexToBase36 = (input: number): string => { + const byte = 48 + Math.floor(input / 7); + return String.fromCharCode(byte + 39 > 122 ? 101 : byte > 57 ? byte + 39 : byte); +}; + +export const makeV2Address = async (addressPrefix: string, key: string): Promise => { + const chars = ["", "", "", "", "", "", "", "", ""]; + let chain = addressPrefix; + let hash = await doubleSHA256(key); + + for (let i = 0; i <= 8; i++) { + chars[i] = hash.substring(0, 2); + hash = await doubleSHA256(hash); + } + + for (let i = 0; i <= 8;) { + const index = parseInt(hash.substring(2 * i, 2 + (2 * i)), 16) % 9; + + if (chars[index] === "") { + hash = await sha256(hash); + } else { + chain += hexToBase36(parseInt(chars[index], 16)); + chars[index] = ""; + i++; + } + } + + return chain; +}; diff --git a/src/krist/wallets/Wallet.ts b/src/krist/wallets/Wallet.ts index 8adfb4f..0a7f84b 100644 --- a/src/krist/wallets/Wallet.ts +++ b/src/krist/wallets/Wallet.ts @@ -1,26 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { v4 as uuid } from "uuid"; - -import { applyWalletFormat, WalletFormatName } from "./WalletFormat"; -import { makeV2Address } from "../AddressAlgo"; - -import { aesGcmDecrypt, aesGcmEncrypt } from "@utils/crypto"; - -import { KristAddressWithNames, lookupAddresses } from "../api/lookup"; - -import { store } from "@app"; -import * as actions from "@actions/WalletsActions"; -import { WalletMap } from "@reducers/WalletsReducer"; - -import { useSelector, shallowEqual } from "react-redux"; -import { RootState } from "@store"; - -import { Mutex } from "async-mutex"; - -import Debug from "debug"; -const debug = Debug("kristweb:wallet"); +import { WalletFormatName } from "."; export interface Wallet { // UUID for this wallet @@ -46,6 +27,8 @@ dontSave?: boolean; // Used to avoid saving when syncing } +export interface WalletMap { [key: string]: Wallet } + /** Properties of Wallet that are required to create a new wallet. */ export type WalletNewKeys = "label" | "category" | "username" | "format" | "dontSave"; export type WalletNew = Pick; @@ -63,349 +46,3 @@ export const WALLET_SYNCABLE_KEYS: WalletSyncableKeys[] = ["balance", "names", "firstSeen", "lastSynced"]; export type WalletSyncable = Pick; - -export interface DecryptedWallet { password: string; privatekey: string } - -/** The limit provided by the Krist server for a single address lookup. In the - * future I may implement batching for these, but for now, this seems like a - * reasonable compromise to limit wallet storage. It should also give us a fair - * upper bound for potential performance issues. */ -export const ADDRESS_LIST_LIMIT = 128; - -/** Get the local storage key for a given wallet. */ -export function getWalletKey(wallet: Wallet): string { - return `wallet2-${wallet.id}`; -} - -/** Extract a wallet ID from a local storage key. */ -const walletKeyRegex = /^wallet2-([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/; -export function extractWalletKey(key: string): [string, string] | undefined { - const [, id] = walletKeyRegex.exec(key) || []; - return id ? [key, id] : undefined; -} - -function loadWallet(id: string, data: string | null) { - if (data === null) // localStorage key was missing - throw new Error("masterPassword.walletStorageCorrupt"); - - try { - const wallet: Wallet = JSON.parse(data); - - // Validate the wallet data actually makes sense - if (!wallet || !wallet.id || wallet.id !== id) - throw new Error("masterPassword.walletStorageCorrupt"); - - return wallet; - } catch (e) { - console.error(e); - - if (e.name === "SyntaxError") // Invalid JSON - throw new Error("masterPassword.errorStorageCorrupt"); - else throw e; // Unknown error - } -} - -/** Loads all available wallets from local storage. */ -export function loadWallets(): WalletMap { - // Find all `wallet2` keys from local storage - const keysToLoad = Object.keys(localStorage) - .map(extractWalletKey) - .filter(k => k !== undefined) as [string, string][]; - - const wallets = keysToLoad.map(([key, id]) => loadWallet(id, localStorage.getItem(key))); - - // Convert to map with wallet IDs - const walletMap: WalletMap = wallets.reduce((obj, w) => ({ ...obj, [w.id]: w }), {}); - - return walletMap; -} - -/** Saves a wallet to local storage, unless it has `dontSave` set. */ -export function saveWallet(wallet: Wallet): void { - if (wallet.dontSave) return; - - const key = getWalletKey(wallet); - const serialised = JSON.stringify(wallet); - localStorage.setItem(key, serialised); -} - -function syncWalletProperties(wallet: Wallet, address: KristAddressWithNames, syncTime: Date): Wallet { - return { - ...wallet, - ...(address.balance !== undefined ? { balance: address.balance } : {}), - ...(address.names !== undefined ? { names: address.names } : {}), - ...(address.firstseen !== undefined ? { firstSeen: address.firstseen } : {}), - lastSynced: syncTime.toISOString() - }; -} - -/** Sync the data for a single wallet from the sync node, save it to local - * storage, and dispatch the change to the Redux store. */ -export async function syncWallet(wallet: Wallet): Promise { - // Fetch the data from the sync node (e.g. balance) - const { address } = wallet; - const lookupResults = await lookupAddresses([address], true); - - const kristAddress = lookupResults[address]; - if (!kristAddress) return; // Skip unsyncable wallet - - syncWalletUpdate(wallet, kristAddress); -} - -/** Given an already synced wallet, save it to local storage, and dispatch the - * change to the Redux store. */ -export function syncWalletUpdate(wallet: Wallet, address: KristAddressWithNames): void { - const syncTime = new Date(); - const updatedWallet = syncWalletProperties(wallet, address, syncTime); - - // Save the wallet to local storage (unless dontSave is set) - saveWallet(updatedWallet); - - // Dispatch the change to the Redux store - store.dispatch(actions.syncWallet(wallet.id, updatedWallet)); -} - -/** Sync the data for all the wallets from the sync node, save it to local - * storage, and dispatch the changes to the Redux store. */ -export async function syncWallets(): Promise { - const { wallets } = store.getState().wallets; - - const syncTime = new Date(); - - // Fetch all the data from the sync node (e.g. balances) - const addresses = Object.values(wallets).map(w => w.address); - const lookupResults = await lookupAddresses(addresses, true); - - // Create a WalletMap with the updated wallet properties - const updatedWallets = Object.entries(wallets).map(([_, wallet]) => { - const kristAddress = lookupResults[wallet.address]; - if (!kristAddress) return wallet; // Skip unsyncable wallets - return syncWalletProperties(wallet, kristAddress, syncTime); - }).reduce((o, wallet) => ({ ...o, [wallet.id]: wallet }), {}); - - // Save the wallets to local storage (unless dontSave is set) - Object.values(updatedWallets).forEach(w => saveWallet(w as Wallet)); - - // Dispatch the changes to the Redux store - store.dispatch(actions.syncWallets(updatedWallets)); -} - -/** - * Adds a new wallet, encrypting its privatekey and password, saving it to - * local storage, and dispatching the changes to the Redux store. - * - * @param addressPrefix - The prefixes of addresses on this node. - * @param masterPassword - The master password used to encrypt the wallet - * password and privatekey. - * @param wallet - The information for the new wallet. - * @param password - The password of the new wallet. - * @param save - Whether or not to save this wallet to local storage. - */ -export async function addWallet( - addressPrefix: string, - masterPassword: string, - wallet: WalletNew, - password: string, - save: boolean -): Promise { - // Calculate the privatekey for the given wallet format - const privatekey = await applyWalletFormat(wallet.format || "kristwallet", password, wallet.username); - const address = await makeV2Address(addressPrefix, privatekey); - - const id = uuid(); - - // Encrypt the password and privatekey. These will be decrypted on-demand. - const encPassword = await aesGcmEncrypt(password, masterPassword); - const encPrivatekey = await aesGcmEncrypt(privatekey, masterPassword); - - const newWallet = { - id, address, - - label: wallet.label?.trim() || undefined, // clean up empty strings - category: wallet.category?.trim() || undefined, - - username: wallet.username, - encPassword, - encPrivatekey, - format: wallet.format, - - ...(save ? {} : { dontSave: true }) - }; - - // Save the wallet to local storage if wanted - if (save) saveWallet(newWallet); - - // Dispatch the changes to the redux store - store.dispatch(actions.addWallet(newWallet)); - - syncWallet(newWallet); - - return newWallet; -} - -/** - * Edits a new wallet, encrypting its privatekey and password, saving it to - * local storage, and dispatching the changes to the Redux store. - * - * @param addressPrefix - The prefixes of addresses on this node. - * @param masterPassword - The master password used to encrypt the wallet - * password and privatekey. - * @param wallet - The old wallet information. - * @param updated - The new wallet information. - * @param password - The password of the updated wallet. - */ -export async function editWallet( - addressPrefix: string, - masterPassword: string, - wallet: Wallet, - updated: WalletNew, - password: string -): Promise { - // Calculate the privatekey for the given wallet format - const privatekey = await applyWalletFormat(updated.format || "kristwallet", password, updated.username); - const address = await makeV2Address(addressPrefix, privatekey); - - // Encrypt the password and privatekey. These will be decrypted on-demand. - const encPassword = await aesGcmEncrypt(password, masterPassword); - const encPrivatekey = await aesGcmEncrypt(privatekey, masterPassword); - - const finalWallet = { - ...wallet, - - label: updated.label?.trim() || undefined, // clean up empty strings - category: updated.category?.trim() || undefined, - - address, - username: updated.username, - encPassword, - encPrivatekey, - format: updated.format - }; - - // Save the updated wallet to local storage - saveWallet(finalWallet); - - // Dispatch the changes to the redux store - store.dispatch(actions.updateWallet(wallet.id, finalWallet)); - - syncWallet(finalWallet); -} - -/** Deletes a wallet, removing it from local storage and dispatching the change - * to the Redux store. */ -export function deleteWallet(wallet: Wallet): void { - const key = getWalletKey(wallet); - localStorage.removeItem(key); - - store.dispatch(actions.removeWallet(wallet.id)); -} - -/** Decrypts a wallet's password and privatekey. */ -export async function decryptWallet(masterPassword: string, wallet: Wallet): Promise { - try { - const decPassword = await aesGcmDecrypt(wallet.encPassword, masterPassword); - const decPrivatekey = await aesGcmDecrypt(wallet.encPrivatekey, masterPassword); - - return { password: decPassword, privatekey: decPrivatekey }; - } catch (e) { - // OperationError usually means decryption failure - if (e.name === "OperationError") return null; - - console.error(e); - return null; - } -} - -/** Finds a wallet in the wallet map by the given Krist address. */ -export function findWalletByAddress(wallets: WalletMap, address?: string): Wallet | null { - if (!address) return null; - - for (const id in wallets) - if (wallets[id].address === address) - return wallets[id]; - - return null; -} - -const recalculationMutex = new Mutex(); -/** If the address prefix changes (e.g. swapping sync node), and we are - * decrypted, recalculate all the addresses with the new prefix. If the prefix - * is unchanged, this does nothing. The changes will be dispatched to the - * Redux store. */ -export async function recalculateWallets(masterPassword: string, wallets: WalletMap, addressPrefix: string): Promise { - const lastPrefix = localStorage.getItem("lastAddressPrefix") || "k"; - if (addressPrefix === lastPrefix) return; - debug("address prefix changed from %s to %s, waiting for mutex...", lastPrefix, addressPrefix); - - // Don't allow more than one recalculation at a time - await recalculationMutex.runExclusive(async () => { - const lastPrefix = localStorage.getItem("lastAddressPrefix") || "k"; - if (addressPrefix === lastPrefix) { - debug("prefix was already reset while we were calculating"); - return; - } - - debug("recalculating all wallets", lastPrefix, addressPrefix); - - // Map of wallet IDs -> new addresses - const updatedWallets: Record = {}; - - // Recalculate all the wallets - for (const id in wallets) { - // Prepare the wallet for recalculation - const wallet = wallets[id]; - const decrypted = await decryptWallet(masterPassword, wallet); - if (!decrypted) - throw new Error(`couldn't decrypt wallet ${wallet.id}!`); - - // Calculate the new address - const privatekey = await applyWalletFormat(wallet.format || "kristwallet", decrypted.password, wallet.username); - const address = await makeV2Address(addressPrefix, privatekey); - - if (wallet.address === address) continue; - - // Prepare the change to be applied - debug("old address: %s - new address: %s", wallet.address, address); - updatedWallets[wallet.id] = address; - } - - // Now that we know everything converted successfully, save the updated - // wallets to local storage - for (const id in wallets) { - const wallet = wallets[id]; - saveWallet({ ...wallet, address: updatedWallets[wallet.id] }); - } - - // Apply all the changes to the Redux store - store.dispatch(actions.recalculateWallets(updatedWallets)); - - debug("recalculation done, saving prefix"); - localStorage.setItem("lastAddressPrefix", addressPrefix); - }); -} - -export interface WalletsHookValues { - wallets: WalletMap; - walletAddressMap: Record; - - addressList: string[]; - joinedAddressList: string; -} - -/** Hook that fetches the wallets from the Redux store. */ -export function useWallets(): WalletsHookValues { - const { wallets } = useSelector((s: RootState) => s.wallets, shallowEqual); - const walletAddressMap = Object.values(wallets) - .reduce((o, wallet) => ({ ...o, [wallet.address]: wallet }), {}); - - // A cheap address list used for deep comparison. It's totally okay to assume - // this list will only change when the addresses will change, as since ES2015, - // object ordering is _basically_ consistent: - // https://stackoverflow.com/a/5525820/1499974 - // https://stackoverflow.com/a/38218582/1499974 - // https://stackoverflow.com/a/23202095/1499974 - const addressList = Object.keys(walletAddressMap); - const joinedAddressList = addressList.join(","); - - return { wallets, walletAddressMap, addressList, joinedAddressList }; -} diff --git a/src/krist/wallets/WalletFormat.ts b/src/krist/wallets/WalletFormat.ts deleted file mode 100644 index b754268..0000000 --- a/src/krist/wallets/WalletFormat.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { sha256 } from "@utils/crypto"; - -export interface WalletFormat { - (password: string, username?: string): Promise; -} - -export type WalletFormatName = "kristwallet" | "kristwallet_username_appendhashes" | "kristwallet_username" | "jwalelset" | "api"; -export const WALLET_FORMATS: Record = { - "kristwallet": async password => - await sha256("KRISTWALLET" + password) + "-000", - - "kristwallet_username_appendhashes": async (password, username) => - await sha256("KRISTWALLETEXTENSION" + await sha256(await sha256(username || "") + "^" + await sha256(password))) + "-000", - - "kristwallet_username": async (password, username) => - await sha256(await sha256(username || "") + "^" + await sha256(password)), - - "jwalelset": async password => - await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(password)))))))))))))))))), - - "api": async password => password -}; -export const ADVANCED_FORMATS: WalletFormatName[] = [ - "kristwallet_username_appendhashes", "kristwallet_username", "jwalelset" -]; - -export const applyWalletFormat = - (format: WalletFormatName, password: string, username?: string): Promise => - WALLET_FORMATS[format](password, username); - -export const formatNeedsUsername = (format: WalletFormatName): boolean => - WALLET_FORMATS[format].length === 2; diff --git a/src/krist/wallets/WalletManager.ts b/src/krist/wallets/WalletManager.ts deleted file mode 100644 index ef54e66..0000000 --- a/src/krist/wallets/WalletManager.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { toHex } from "@utils"; -import { aesGcmEncrypt, aesGcmDecrypt } from "@utils/crypto"; - -import { store } from "@app"; -import * as actions from "@actions/WalletManagerActions"; - -import { TranslatedError } from "@utils/i18n"; - -/** Verifies that the given master password is correct, and dispatches the - * auth action to the Redux store. */ -export async function authMasterPassword( - salt: string | undefined, - tester: string | undefined, - password: string -): Promise { - if (!password) - throw new TranslatedError("masterPassword.errorPasswordRequired"); - if (!salt || !tester) - throw new TranslatedError("masterPassword.errorPasswordUnset"); - - try { - // Attempt to decrypt the tester with the given password - const testerDec = await aesGcmDecrypt(tester, password); - - // Verify that the decrypted tester is equal to the salt, if not, the - // provided master password is incorrect. - if (testerDec !== salt) - throw new TranslatedError("masterPassword.errorPasswordIncorrect"); - } catch (e) { - // OperationError usually means decryption failure - if (e.name === "OperationError") - throw new TranslatedError("masterPassword.errorPasswordIncorrect"); - else throw e; - } - - // Dispatch the auth state changes to the Redux store - store.dispatch(actions.authMasterPassword(password)); -} - -/** Generates a salt and tester, sets the master password, and dispatches the - * action to the Redux store. */ -export async function setMasterPassword(password: string): Promise { - if (!password) - throw new TranslatedError("masterPassword.errorPasswordRequired"); - - // Generate the salt (to be encrypted with the master password) - const salt = window.crypto.getRandomValues(new Uint8Array(32)); - const saltHex = toHex(salt); - - // Generate the encryption tester - const tester = await aesGcmEncrypt(saltHex, password); - - // Store them in local storage - localStorage.setItem("salt", saltHex); - localStorage.setItem("tester", tester); - - // Dispatch the auth state changes to the Redux store - store.dispatch(actions.setMasterPassword(saltHex, tester, password)); -} diff --git a/src/krist/wallets/functions/addWallet.ts b/src/krist/wallets/functions/addWallet.ts new file mode 100644 index 0000000..592b236 --- /dev/null +++ b/src/krist/wallets/functions/addWallet.ts @@ -0,0 +1,63 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { v4 as uuid } from "uuid"; + +import { store } from "@app"; +import * as actions from "@actions/WalletsActions"; + +import { aesGcmEncrypt } from "@utils/crypto"; + +import { Wallet, WalletNew, saveWallet, syncWallet, calculateAddress } from ".."; + +/** + * Adds a new wallet, encrypting its privatekey and password, saving it to + * local storage, and dispatching the changes to the Redux store. + * + * @param addressPrefix - The prefixes of addresses on this node. + * @param masterPassword - The master password used to encrypt the wallet + * password and privatekey. + * @param wallet - The information for the new wallet. + * @param password - The password of the new wallet. + * @param save - Whether or not to save this wallet to local storage. + */ +export async function addWallet( + addressPrefix: string, + masterPassword: string, + wallet: WalletNew, + password: string, + save: boolean +): Promise { + // Calculate the privatekey and address for the given wallet format + const { privatekey, address } = await calculateAddress(addressPrefix, wallet, password); + + const id = uuid(); + + // Encrypt the password and privatekey. These will be decrypted on-demand. + const encPassword = await aesGcmEncrypt(password, masterPassword); + const encPrivatekey = await aesGcmEncrypt(privatekey, masterPassword); + + const newWallet = { + id, address, + + label: wallet.label?.trim() || undefined, // clean up empty strings + category: wallet.category?.trim() || undefined, + + username: wallet.username, + encPassword, + encPrivatekey, + format: wallet.format, + + ...(save ? {} : { dontSave: true }) + }; + + // Save the wallet to local storage if wanted + if (save) saveWallet(newWallet); + + // Dispatch the changes to the redux store + store.dispatch(actions.addWallet(newWallet)); + + syncWallet(newWallet); + + return newWallet; +} diff --git a/src/krist/wallets/functions/decryptWallet.ts b/src/krist/wallets/functions/decryptWallet.ts new file mode 100644 index 0000000..3c87a69 --- /dev/null +++ b/src/krist/wallets/functions/decryptWallet.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { aesGcmDecrypt } from "@utils/crypto"; + +import { Wallet } from ".."; + +export interface DecryptedWallet { password: string; privatekey: string } + +/** Decrypts a wallet's password and privatekey. */ +export async function decryptWallet( + masterPassword: string, + wallet: Wallet +): Promise { + try { + const decPassword = await aesGcmDecrypt(wallet.encPassword, masterPassword); + const decPrivatekey = await aesGcmDecrypt(wallet.encPrivatekey, masterPassword); + + return { password: decPassword, privatekey: decPrivatekey }; + } catch (e) { + // OperationError usually means decryption failure + if (e.name === "OperationError") return null; + + console.error(e); + return null; + } +} diff --git a/src/krist/wallets/functions/editWallet.ts b/src/krist/wallets/functions/editWallet.ts new file mode 100644 index 0000000..3f62120 --- /dev/null +++ b/src/krist/wallets/functions/editWallet.ts @@ -0,0 +1,56 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { store } from "@app"; +import * as actions from "@actions/WalletsActions"; + +import { aesGcmEncrypt } from "@utils/crypto"; + +import { Wallet, WalletNew, saveWallet, syncWallet, calculateAddress } from ".."; + +/** + * Edits a new wallet, encrypting its privatekey and password, saving it to + * local storage, and dispatching the changes to the Redux store. + * + * @param addressPrefix - The prefixes of addresses on this node. + * @param masterPassword - The master password used to encrypt the wallet + * password and privatekey. + * @param wallet - The old wallet information. + * @param updated - The new wallet information. + * @param password - The password of the updated wallet. + */ +export async function editWallet( + addressPrefix: string, + masterPassword: string, + wallet: Wallet, + updated: WalletNew, + password: string +): Promise { + // Calculate the privatekey and address for the given wallet format + const { privatekey, address } = await calculateAddress(addressPrefix, updated, password); + + // Encrypt the password and privatekey. These will be decrypted on-demand. + const encPassword = await aesGcmEncrypt(password, masterPassword); + const encPrivatekey = await aesGcmEncrypt(privatekey, masterPassword); + + const finalWallet = { + ...wallet, + + label: updated.label?.trim() || undefined, // clean up empty strings + category: updated.category?.trim() || undefined, + + address, + username: updated.username, + encPassword, + encPrivatekey, + format: updated.format + }; + + // Save the updated wallet to local storage + saveWallet(finalWallet); + + // Dispatch the changes to the redux store + store.dispatch(actions.updateWallet(wallet.id, finalWallet)); + + syncWallet(finalWallet); +} diff --git a/src/krist/wallets/functions/recalculateWallets.ts b/src/krist/wallets/functions/recalculateWallets.ts new file mode 100644 index 0000000..27cbec2 --- /dev/null +++ b/src/krist/wallets/functions/recalculateWallets.ts @@ -0,0 +1,69 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { store } from "@app"; +import * as actions from "@actions/WalletsActions"; + +import { WalletMap, decryptWallet, saveWallet, calculateAddress } from ".."; + +import { Mutex } from "async-mutex"; + +import Debug from "debug"; +const debug = Debug("kristweb:wallet"); + +const recalculationMutex = new Mutex(); + +/** If the address prefix changes (e.g. swapping sync node), and we are + * decrypted, recalculate all the addresses with the new prefix. If the prefix + * is unchanged, this does nothing. The changes will be dispatched to the + * Redux store. */ +export async function recalculateWallets(masterPassword: string, wallets: WalletMap, addressPrefix: string): Promise { + const lastPrefix = localStorage.getItem("lastAddressPrefix") || "k"; + if (addressPrefix === lastPrefix) return; + debug("address prefix changed from %s to %s, waiting for mutex...", lastPrefix, addressPrefix); + + // Don't allow more than one recalculation at a time + await recalculationMutex.runExclusive(async () => { + const lastPrefix = localStorage.getItem("lastAddressPrefix") || "k"; + if (addressPrefix === lastPrefix) { + debug("prefix was already reset while we were calculating"); + return; + } + + debug("recalculating all wallets", lastPrefix, addressPrefix); + + // Map of wallet IDs -> new addresses + const updatedWallets: Record = {}; + + // Recalculate all the wallets + for (const id in wallets) { + // Prepare the wallet for recalculation + const wallet = wallets[id]; + const decrypted = await decryptWallet(masterPassword, wallet); + if (!decrypted) + throw new Error(`couldn't decrypt wallet ${wallet.id}!`); + + // Calculate the new address + const { address } = await calculateAddress(addressPrefix, wallet, decrypted.password); + + if (wallet.address === address) continue; + + // Prepare the change to be applied + debug("old address: %s - new address: %s", wallet.address, address); + updatedWallets[wallet.id] = address; + } + + // Now that we know everything converted successfully, save the updated + // wallets to local storage + for (const id in wallets) { + const wallet = wallets[id]; + saveWallet({ ...wallet, address: updatedWallets[wallet.id] }); + } + + // Apply all the changes to the Redux store + store.dispatch(actions.recalculateWallets(updatedWallets)); + + debug("recalculation done, saving prefix"); + localStorage.setItem("lastAddressPrefix", addressPrefix); + }); +} diff --git a/src/krist/wallets/functions/syncWallets.ts b/src/krist/wallets/functions/syncWallets.ts new file mode 100644 index 0000000..e55c218 --- /dev/null +++ b/src/krist/wallets/functions/syncWallets.ts @@ -0,0 +1,77 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { store } from "@app"; +import * as actions from "@actions/WalletsActions"; + +import { KristAddressWithNames, lookupAddresses } from "../../api/lookup"; + +import { Wallet, saveWallet } from ".."; + +function syncWalletProperties( + wallet: Wallet, + address: KristAddressWithNames, + syncTime: Date +): Wallet { + return { + ...wallet, + ...(address.balance !== undefined ? { balance: address.balance } : {}), + ...(address.names !== undefined ? { names: address.names } : {}), + ...(address.firstseen !== undefined ? { firstSeen: address.firstseen } : {}), + lastSynced: syncTime.toISOString() + }; +} + +/** Sync the data for a single wallet from the sync node, save it to local + * storage, and dispatch the change to the Redux store. */ +export async function syncWallet(wallet: Wallet): Promise { + // Fetch the data from the sync node (e.g. balance) + const { address } = wallet; + const lookupResults = await lookupAddresses([address], true); + + const kristAddress = lookupResults[address]; + if (!kristAddress) return; // Skip unsyncable wallet + + syncWalletUpdate(wallet, kristAddress); +} + +/** Given an already synced wallet, save it to local storage, and dispatch the + * change to the Redux store. */ +export function syncWalletUpdate( + wallet: Wallet, + address: KristAddressWithNames +): void { + const syncTime = new Date(); + const updatedWallet = syncWalletProperties(wallet, address, syncTime); + + // Save the wallet to local storage (unless dontSave is set) + saveWallet(updatedWallet); + + // Dispatch the change to the Redux store + store.dispatch(actions.syncWallet(wallet.id, updatedWallet)); +} + +/** Sync the data for all the wallets from the sync node, save it to local + * storage, and dispatch the changes to the Redux store. */ +export async function syncWallets(): Promise { + const { wallets } = store.getState().wallets; + + const syncTime = new Date(); + + // Fetch all the data from the sync node (e.g. balances) + const addresses = Object.values(wallets).map(w => w.address); + const lookupResults = await lookupAddresses(addresses, true); + + // Create a WalletMap with the updated wallet properties + const updatedWallets = Object.entries(wallets).map(([_, wallet]) => { + const kristAddress = lookupResults[wallet.address]; + if (!kristAddress) return wallet; // Skip unsyncable wallets + return syncWalletProperties(wallet, kristAddress, syncTime); + }).reduce((o, wallet) => ({ ...o, [wallet.id]: wallet }), {}); + + // Save the wallets to local storage (unless dontSave is set) + Object.values(updatedWallets).forEach(w => saveWallet(w as Wallet)); + + // Dispatch the changes to the Redux store + store.dispatch(actions.syncWallets(updatedWallets)); +} diff --git a/src/krist/wallets/index.ts b/src/krist/wallets/index.ts new file mode 100644 index 0000000..712fdac --- /dev/null +++ b/src/krist/wallets/index.ts @@ -0,0 +1,13 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +export * from "./Wallet"; +export * from "./walletFormats"; +export * from "./functions/addWallet"; +export * from "./functions/editWallet"; +export * from "./functions/syncWallets"; +export * from "./functions/decryptWallet"; +export * from "./functions/recalculateWallets"; +export * from "./masterPassword"; +export * from "./walletStorage"; +export * from "./utils"; diff --git a/src/krist/wallets/masterPassword.ts b/src/krist/wallets/masterPassword.ts new file mode 100644 index 0000000..7407b96 --- /dev/null +++ b/src/krist/wallets/masterPassword.ts @@ -0,0 +1,62 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { toHex } from "@utils"; +import { aesGcmEncrypt, aesGcmDecrypt } from "@utils/crypto"; + +import { store } from "@app"; +import * as actions from "@actions/MasterPasswordActions"; + +import { TranslatedError } from "@utils/i18n"; + +/** Verifies that the given master password is correct, and dispatches the + * auth action to the Redux store. */ +export async function authMasterPassword( + salt: string | undefined, + tester: string | undefined, + password: string +): Promise { + if (!password) + throw new TranslatedError("masterPassword.errorPasswordRequired"); + if (!salt || !tester) + throw new TranslatedError("masterPassword.errorPasswordUnset"); + + try { + // Attempt to decrypt the tester with the given password + const testerDec = await aesGcmDecrypt(tester, password); + + // Verify that the decrypted tester is equal to the salt, if not, the + // provided master password is incorrect. + if (testerDec !== salt) + throw new TranslatedError("masterPassword.errorPasswordIncorrect"); + } catch (e) { + // OperationError usually means decryption failure + if (e.name === "OperationError") + throw new TranslatedError("masterPassword.errorPasswordIncorrect"); + else throw e; + } + + // Dispatch the auth state changes to the Redux store + store.dispatch(actions.authMasterPassword(password)); +} + +/** Generates a salt and tester, sets the master password, and dispatches the + * action to the Redux store. */ +export async function setMasterPassword(password: string): Promise { + if (!password) + throw new TranslatedError("masterPassword.errorPasswordRequired"); + + // Generate the salt (to be encrypted with the master password) + const salt = window.crypto.getRandomValues(new Uint8Array(32)); + const saltHex = toHex(salt); + + // Generate the encryption tester + const tester = await aesGcmEncrypt(saltHex, password); + + // Store them in local storage + localStorage.setItem("salt", saltHex); + localStorage.setItem("tester", tester); + + // Dispatch the auth state changes to the Redux store + store.dispatch(actions.setMasterPassword(saltHex, tester, password)); +} diff --git a/src/krist/wallets/utils.ts b/src/krist/wallets/utils.ts new file mode 100644 index 0000000..aa66110 --- /dev/null +++ b/src/krist/wallets/utils.ts @@ -0,0 +1,82 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useSelector, shallowEqual } from "react-redux"; +import { RootState } from "@store"; + +import { Wallet, WalletNew, WalletMap, WalletFormatName, applyWalletFormat } from "."; +import { makeV2Address } from "../addressAlgo"; + +export interface WalletsHookResponse { + wallets: WalletMap; + walletAddressMap: Record; + + addressList: string[]; + joinedAddressList: string; +} + +/** Finds a wallet in the wallet map by the given Krist address. */ +export function findWalletByAddress( + wallets: WalletMap, + address?: string +): Wallet | null { + if (!address) return null; + + for (const id in wallets) { + if (wallets[id].address === address) { + return wallets[id]; + } + } + + return null; +} + +/** Hook that fetches the wallets from the Redux store. */ +export function useWallets(): WalletsHookResponse { + const { wallets } = useSelector((s: RootState) => s.wallets, shallowEqual); + const walletAddressMap = Object.values(wallets) + .reduce((o, wallet) => ({ ...o, [wallet.address]: wallet }), {}); + + // A cheap address list used for deep comparison. It's totally okay to assume + // this list will only change when the addresses will change, as since ES2015, + // object ordering is _basically_ consistent: + // https://stackoverflow.com/a/5525820/1499974 + // https://stackoverflow.com/a/38218582/1499974 + // https://stackoverflow.com/a/23202095/1499974 + const addressList = Object.keys(walletAddressMap); + const joinedAddressList = addressList.join(","); + + return { wallets, walletAddressMap, addressList, joinedAddressList }; +} + +/** Almost anywhere you'd want to apply a wallet format, you'd also want to + * calculate the v2 address, so just do both at once! */ +export async function calculateAddress( + addressPrefix: string, + walletOrFormat: Wallet | WalletNew | WalletFormatName | undefined, + password: string, + username?: string +): Promise<{ + privatekey: string; + address: string; +}> { + if (walletOrFormat === undefined || typeof walletOrFormat === "string") { + // We were passed a wallet format (or undefined, which is assumed to be + // the default format, since it's not a Wallet instance) + const format: WalletFormatName = walletOrFormat || "kristwallet"; + + const privatekey = await applyWalletFormat(format || "kristwallet", password, username); + const address = await makeV2Address(addressPrefix, privatekey); + return { privatekey, address }; + } else { + // We were passed a Wallet or WalletNew instance + if (typeof walletOrFormat !== "object" || walletOrFormat === null) + throw new Error("Missing `walletOrFormat` in `calculateAddress`!"); + + const { format, username } = walletOrFormat as WalletNew; + + const privatekey = await applyWalletFormat(format || "kristwallet", password, username); + const address = await makeV2Address(addressPrefix, privatekey); + return { privatekey, address }; + } +} diff --git a/src/krist/wallets/walletFormats.ts b/src/krist/wallets/walletFormats.ts new file mode 100644 index 0000000..b754268 --- /dev/null +++ b/src/krist/wallets/walletFormats.ts @@ -0,0 +1,35 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { sha256 } from "@utils/crypto"; + +export interface WalletFormat { + (password: string, username?: string): Promise; +} + +export type WalletFormatName = "kristwallet" | "kristwallet_username_appendhashes" | "kristwallet_username" | "jwalelset" | "api"; +export const WALLET_FORMATS: Record = { + "kristwallet": async password => + await sha256("KRISTWALLET" + password) + "-000", + + "kristwallet_username_appendhashes": async (password, username) => + await sha256("KRISTWALLETEXTENSION" + await sha256(await sha256(username || "") + "^" + await sha256(password))) + "-000", + + "kristwallet_username": async (password, username) => + await sha256(await sha256(username || "") + "^" + await sha256(password)), + + "jwalelset": async password => + await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(await sha256(password)))))))))))))))))), + + "api": async password => password +}; +export const ADVANCED_FORMATS: WalletFormatName[] = [ + "kristwallet_username_appendhashes", "kristwallet_username", "jwalelset" +]; + +export const applyWalletFormat = + (format: WalletFormatName, password: string, username?: string): Promise => + WALLET_FORMATS[format](password, username); + +export const formatNeedsUsername = (format: WalletFormatName): boolean => + WALLET_FORMATS[format].length === 2; diff --git a/src/krist/wallets/walletStorage.ts b/src/krist/wallets/walletStorage.ts new file mode 100644 index 0000000..90af2fe --- /dev/null +++ b/src/krist/wallets/walletStorage.ts @@ -0,0 +1,81 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { store } from "@app"; +import * as actions from "@actions/WalletsActions"; + +import { TranslatedError } from "@utils/i18n"; + +import { Wallet, WalletMap } from "."; + +/** The limit provided by the Krist server for a single address lookup. In the + * future I may implement batching for these, but for now, this seems like a + * reasonable compromise to limit wallet storage. It should also give us a fair + * upper bound for potential performance issues. */ +export const ADDRESS_LIST_LIMIT = 128; + +/** Get the local storage key for a given wallet. */ +export function getWalletKey(wallet: Wallet): string { + return `wallet2-${wallet.id}`; +} + +/** Extract a wallet ID from a local storage key. */ +const walletKeyRegex = /^wallet2-([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/; +export function extractWalletKey(key: string): [string, string] | undefined { + const [, id] = walletKeyRegex.exec(key) || []; + return id ? [key, id] : undefined; +} + +function loadWallet(id: string, data: string | null) { + if (data === null) // localStorage key was missing + throw new TranslatedError("masterPassword.walletStorageCorrupt"); + + try { + const wallet: Wallet = JSON.parse(data); + + // Validate the wallet data actually makes sense + if (!wallet || !wallet.id || wallet.id !== id) + throw new TranslatedError("masterPassword.walletStorageCorrupt"); + + return wallet; + } catch (e) { + console.error(e); + + if (e.name === "SyntaxError") // Invalid JSON + throw new TranslatedError("masterPassword.errorStorageCorrupt"); + else throw e; // Unknown error + } +} + +/** Loads all available wallets from local storage. */ +export function loadWallets(): WalletMap { + // Find all `wallet2` keys from local storage + const keysToLoad = Object.keys(localStorage) + .map(extractWalletKey) + .filter(k => k !== undefined) as [string, string][]; + + const wallets = keysToLoad.map(([key, id]) => loadWallet(id, localStorage.getItem(key))); + + // Convert to map with wallet IDs + const walletMap: WalletMap = wallets.reduce((obj, w) => ({ ...obj, [w.id]: w }), {}); + + return walletMap; +} + +/** Saves a wallet to local storage, unless it has `dontSave` set. */ +export function saveWallet(wallet: Wallet): void { + if (wallet.dontSave) return; + + const key = getWalletKey(wallet); + const serialised = JSON.stringify(wallet); + localStorage.setItem(key, serialised); +} + +/** Deletes a wallet, removing it from local storage and dispatching the change + * to the Redux store. */ +export function deleteWallet(wallet: Wallet): void { + const key = getWalletKey(wallet); + localStorage.removeItem(key); + + store.dispatch(actions.removeWallet(wallet.id)); +} diff --git a/src/layout/sidebar/SidebarTotalBalance.tsx b/src/layout/sidebar/SidebarTotalBalance.tsx index 975e275..8ed727f 100644 --- a/src/layout/sidebar/SidebarTotalBalance.tsx +++ b/src/layout/sidebar/SidebarTotalBalance.tsx @@ -4,7 +4,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; import { KristValue } from "@comp/krist/KristValue"; export function SidebarTotalBalance(): JSX.Element { diff --git a/src/pages/addresses/AddressButtonRow.tsx b/src/pages/addresses/AddressButtonRow.tsx index df45569..c15fb97 100644 --- a/src/pages/addresses/AddressButtonRow.tsx +++ b/src/pages/addresses/AddressButtonRow.tsx @@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"; import { KristAddressWithNames } from "@api/lookup"; -import { Wallet } from "@wallets/Wallet"; +import { Wallet } from "@wallets"; import { WalletEditButton } from "../wallets/WalletEditButton"; interface Props { diff --git a/src/pages/addresses/AddressPage.tsx b/src/pages/addresses/AddressPage.tsx index 3a7dcbc..d4e34c9 100644 --- a/src/pages/addresses/AddressPage.tsx +++ b/src/pages/addresses/AddressPage.tsx @@ -16,7 +16,7 @@ import * as api from "@api"; import { lookupAddress, KristAddressWithNames } from "@api/lookup"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; import { AddressButtonRow } from "./AddressButtonRow"; import { AddressTransactionsCard } from "./AddressTransactionsCard"; diff --git a/src/pages/backup/backupFormats.ts b/src/pages/backup/backupFormats.ts index a74ec0f..950d4e4 100644 --- a/src/pages/backup/backupFormats.ts +++ b/src/pages/backup/backupFormats.ts @@ -1,8 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { Wallet } from "@wallets/Wallet"; -import { WalletFormatName } from "@wallets/WalletFormat"; +import { Wallet } from "@wallets"; // The values here are the translation keys for the formats. export enum BackupFormatType { diff --git a/src/pages/backup/backupImportV1.ts b/src/pages/backup/backupImportV1.ts index c161e36..dd9ba6a 100644 --- a/src/pages/backup/backupImportV1.ts +++ b/src/pages/backup/backupImportV1.ts @@ -5,13 +5,11 @@ import { BackupWalletError, BackupResults, MessageType } from "./backupResults"; import { backupDecryptValue } from "./backupImport"; -import { WalletMap } from "@reducers/WalletsReducer"; import { + ADDRESS_LIST_LIMIT, WALLET_FORMATS, ADVANCED_FORMATS, WalletFormatName, formatNeedsUsername, - applyWalletFormat -} from "@wallets/WalletFormat"; -import { ADDRESS_LIST_LIMIT } from "@wallets/Wallet"; -import { makeV2Address } from "@krist/AddressAlgo"; + WalletMap, calculateAddress +} from "@wallets"; import { isPlainObject, memoize } from "lodash-es"; import to from "await-to-js"; @@ -138,8 +136,12 @@ // WALLET IMPORT PREPARATION/VALIDATION // --------------------------------------------------------------------------- // Calculate the address in advance, to check for existing wallets - const privatekey = await applyWalletFormat(format || "kristwallet", password, username); - const address = await makeV2Address(addressPrefix, privatekey); + const { privatekey, address } = await calculateAddress( + addressPrefix, + format || "kristwallet", + password, + username + ); // Check that our calculated privatekey is actually equal to the stored // masterkey. In practice these should never be different. diff --git a/src/pages/backup/backupResults.ts b/src/pages/backup/backupResults.ts index 5f9028d..a174f9a 100644 --- a/src/pages/backup/backupResults.ts +++ b/src/pages/backup/backupResults.ts @@ -5,7 +5,7 @@ import { TranslatedError } from "@utils/i18n"; -import { Wallet } from "@wallets/Wallet"; +import { Wallet } from "@wallets"; import Debug from "debug"; const debug = Debug("kristweb:backup-results"); diff --git a/src/pages/dashboard/TransactionsCard.tsx b/src/pages/dashboard/TransactionsCard.tsx index 0a9e9b1..bd6035c 100644 --- a/src/pages/dashboard/TransactionsCard.tsx +++ b/src/pages/dashboard/TransactionsCard.tsx @@ -11,8 +11,7 @@ import { lookupTransactions, LookupTransactionsResponse } from "@api/lookup"; import { useSyncNode } from "@api"; -import { useWallets } from "@wallets/Wallet"; -import { WalletMap } from "@reducers/WalletsReducer"; +import { WalletMap, useWallets } from "@wallets"; import { SmallResult } from "@comp/results/SmallResult"; diff --git a/src/pages/dashboard/WalletItem.tsx b/src/pages/dashboard/WalletItem.tsx index 67b18eb..386041e 100644 --- a/src/pages/dashboard/WalletItem.tsx +++ b/src/pages/dashboard/WalletItem.tsx @@ -4,7 +4,7 @@ import React from "react"; import { Row, Col } from "antd"; -import { Wallet } from "@wallets/Wallet"; +import { Wallet } from "@wallets"; import { KristValue } from "@comp/krist/KristValue"; diff --git a/src/pages/dashboard/WalletOverviewCard.tsx b/src/pages/dashboard/WalletOverviewCard.tsx index a618365..845b6b4 100644 --- a/src/pages/dashboard/WalletOverviewCard.tsx +++ b/src/pages/dashboard/WalletOverviewCard.tsx @@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; -import { Wallet, useWallets } from "@wallets/Wallet"; +import { Wallet, useWallets } from "@wallets"; import { KristValue } from "@comp/krist/KristValue"; import { Statistic } from "@comp/Statistic"; diff --git a/src/pages/names/NameButtonRow.tsx b/src/pages/names/NameButtonRow.tsx index 464556d..108910d 100644 --- a/src/pages/names/NameButtonRow.tsx +++ b/src/pages/names/NameButtonRow.tsx @@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"; import { KristName } from "@api/types"; -import { Wallet } from "@wallets/Wallet"; +import { Wallet } from "@wallets"; interface Props { name: KristName; diff --git a/src/pages/names/NamePage.tsx b/src/pages/names/NamePage.tsx index ae44f9a..9c36d6d 100644 --- a/src/pages/names/NamePage.tsx +++ b/src/pages/names/NamePage.tsx @@ -22,7 +22,7 @@ import * as api from "@api"; import { KristName } from "@api/types"; import { LookupTransactionType as LookupTXType } from "@api/lookup"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; import { useBooleanSetting } from "@utils/settings"; import { NameButtonRow } from "./NameButtonRow"; diff --git a/src/pages/names/NamesPage.tsx b/src/pages/names/NamesPage.tsx index fc18908..d121570 100644 --- a/src/pages/names/NamesPage.tsx +++ b/src/pages/names/NamesPage.tsx @@ -13,7 +13,7 @@ import { APIErrorResult } from "@comp/results/APIErrorResult"; import { NamesTable } from "./NamesTable"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; import { useBooleanSetting } from "@utils/settings"; import { useLinkedPagination } from "@utils/table"; diff --git a/src/pages/transactions/TransactionsPage.tsx b/src/pages/transactions/TransactionsPage.tsx index 549d4d2..4df9aab 100644 --- a/src/pages/transactions/TransactionsPage.tsx +++ b/src/pages/transactions/TransactionsPage.tsx @@ -16,7 +16,7 @@ import { APIErrorResult } from "@comp/results/APIErrorResult"; import { TransactionsTable } from "./TransactionsTable"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; import { useBooleanSetting } from "@utils/settings"; import { useLinkedPagination } from "@utils/table"; import { KristNameLink } from "@comp/names/KristNameLink"; diff --git a/src/pages/wallets/AddWalletModal.tsx b/src/pages/wallets/AddWalletModal.tsx index 8e09531..97d33ef 100644 --- a/src/pages/wallets/AddWalletModal.tsx +++ b/src/pages/wallets/AddWalletModal.tsx @@ -15,10 +15,11 @@ import { CopyInputButton } from "@comp/CopyInputButton"; import { SelectWalletCategory } from "@comp/wallets/SelectWalletCategory"; -import { WalletFormatName, applyWalletFormat, formatNeedsUsername } from "@wallets/WalletFormat"; import { SelectWalletFormat } from "@comp/wallets/SelectWalletFormat"; -import { makeV2Address } from "@krist/AddressAlgo"; -import { useWallets, addWallet, decryptWallet, editWallet, Wallet, ADDRESS_LIST_LIMIT } from "@wallets/Wallet"; +import { + Wallet, WalletFormatName, calculateAddress, formatNeedsUsername, + useWallets, addWallet, decryptWallet, editWallet, ADDRESS_LIST_LIMIT +} from "@wallets"; const { Text } = Typography; const { useBreakpoint } = Grid; @@ -50,7 +51,7 @@ const initialFormat = editing?.format || "kristwallet"; // Required to encrypt new wallets - const masterPassword = useSelector((s: RootState) => s.walletManager.masterPassword); + const masterPassword = useSelector((s: RootState) => s.masterPassword.masterPassword); // Required to check for existing wallets const { wallets } = useWallets(); const addressPrefix = useSelector((s: RootState) => s.node.currency.address_prefix); @@ -148,8 +149,7 @@ /** Update the 'Wallet address' field */ const updateCalculatedAddress = useCallback(async function(format: WalletFormatName | undefined, password: string, username?: string) { - const privatekey = await applyWalletFormat(format || "kristwallet", password, username); - const address = await makeV2Address(addressPrefix, privatekey); + const { address } = await calculateAddress(addressPrefix, format, password, username); setCalculatedAddress(address); }, [addressPrefix]); diff --git a/src/pages/wallets/WalletEditButton.tsx b/src/pages/wallets/WalletEditButton.tsx index e77b3cc..6a9dba5 100644 --- a/src/pages/wallets/WalletEditButton.tsx +++ b/src/pages/wallets/WalletEditButton.tsx @@ -6,7 +6,7 @@ import { AuthorisedAction } from "@comp/auth/AuthorisedAction"; import { AddWalletModal } from "./AddWalletModal"; -import { Wallet } from "@wallets/Wallet"; +import { Wallet } from "@wallets"; interface Props { wallet: Wallet; diff --git a/src/pages/wallets/WalletsPage.tsx b/src/pages/wallets/WalletsPage.tsx index 0bc198c..d4079c6 100644 --- a/src/pages/wallets/WalletsPage.tsx +++ b/src/pages/wallets/WalletsPage.tsx @@ -12,7 +12,7 @@ import { AddWalletModal } from "./AddWalletModal"; import { WalletsTable } from "./WalletsTable"; -import { useWallets } from "@wallets/Wallet"; +import { useWallets } from "@wallets"; import "./WalletsPage.less"; diff --git a/src/pages/wallets/WalletsTable.tsx b/src/pages/wallets/WalletsTable.tsx index 4080ec7..b8f248c 100644 --- a/src/pages/wallets/WalletsTable.tsx +++ b/src/pages/wallets/WalletsTable.tsx @@ -13,7 +13,7 @@ import { WalletEditButton } from "./WalletEditButton"; import { AddWalletModal } from "./AddWalletModal"; -import { Wallet, deleteWallet, useWallets } from "@wallets/Wallet"; +import { Wallet, deleteWallet, useWallets } from "@wallets"; import { keyedNullSort, localeSort } from "@utils"; diff --git a/src/store/actions/MasterPasswordActions.ts b/src/store/actions/MasterPasswordActions.ts new file mode 100644 index 0000000..8ed9ab0 --- /dev/null +++ b/src/store/actions/MasterPasswordActions.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { createAction } from "typesafe-actions"; + +import * as constants from "../constants"; + +export interface AuthMasterPasswordPayload { password: string } +export const authMasterPassword = createAction(constants.AUTH_MASTER_PASSWORD, + (password): AuthMasterPasswordPayload => + ({ password }))(); + +export interface SetMasterPasswordPayload { + salt: string; + tester: string; + password: string; +} +export const setMasterPassword = createAction(constants.SET_MASTER_PASSWORD, + (salt, tester, password): SetMasterPasswordPayload => + ({ salt, tester, password }))(); diff --git a/src/store/actions/WalletManagerActions.ts b/src/store/actions/WalletManagerActions.ts deleted file mode 100644 index 8ed9ab0..0000000 --- a/src/store/actions/WalletManagerActions.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { createAction } from "typesafe-actions"; - -import * as constants from "../constants"; - -export interface AuthMasterPasswordPayload { password: string } -export const authMasterPassword = createAction(constants.AUTH_MASTER_PASSWORD, - (password): AuthMasterPasswordPayload => - ({ password }))(); - -export interface SetMasterPasswordPayload { - salt: string; - tester: string; - password: string; -} -export const setMasterPassword = createAction(constants.SET_MASTER_PASSWORD, - (salt, tester, password): SetMasterPasswordPayload => - ({ salt, tester, password }))(); diff --git a/src/store/actions/WalletsActions.ts b/src/store/actions/WalletsActions.ts index 16226ba..f4f006b 100644 --- a/src/store/actions/WalletsActions.ts +++ b/src/store/actions/WalletsActions.ts @@ -5,8 +5,7 @@ import * as constants from "../constants"; -import { WalletMap } from "@reducers/WalletsReducer"; -import { Wallet, WalletSyncable, WalletUpdatable } from "@wallets/Wallet"; +import { Wallet, WalletMap, WalletSyncable, WalletUpdatable } from "@wallets"; export interface LoadWalletsPayload { wallets: WalletMap } export const loadWallets = createAction(constants.LOAD_WALLETS, diff --git a/src/store/actions/index.ts b/src/store/actions/index.ts index 6d8996f..64b5807 100644 --- a/src/store/actions/index.ts +++ b/src/store/actions/index.ts @@ -1,14 +1,14 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import * as walletManagerActions from "./WalletManagerActions"; +import * as masterPasswordActions from "./MasterPasswordActions"; import * as walletsActions from "./WalletsActions"; import * as settingsActions from "./SettingsActions"; import * as websocketActions from "./WebsocketActions"; import * as nodeActions from "./NodeActions"; const RootAction = { - walletManager: walletManagerActions, + masterPassword: masterPasswordActions, wallets: walletsActions, settings: settingsActions, websocket: websocketActions, diff --git a/src/store/init.ts b/src/store/init.ts index d1a1ad4..29a3cf4 100644 --- a/src/store/init.ts +++ b/src/store/init.ts @@ -1,7 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { getInitialWalletManagerState } from "./reducers/WalletManagerReducer"; +import { getInitialMasterPasswordState } from "./reducers/MasterPasswordReducer"; import { getInitialWalletsState } from "./reducers/WalletsReducer"; import { getInitialSettingsState } from "./reducers/SettingsReducer"; import { getInitialNodeState } from "./reducers/NodeReducer"; @@ -15,7 +15,7 @@ export const initStore = (): Store => createStore( rootReducer, { - walletManager: getInitialWalletManagerState(), + masterPassword: getInitialMasterPasswordState(), wallets: getInitialWalletsState(), settings: getInitialSettingsState(), node: getInitialNodeState() diff --git a/src/store/reducers/MasterPasswordReducer.ts b/src/store/reducers/MasterPasswordReducer.ts new file mode 100644 index 0000000..a1736f6 --- /dev/null +++ b/src/store/reducers/MasterPasswordReducer.ts @@ -0,0 +1,58 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { createReducer, ActionType } from "typesafe-actions"; +import { authMasterPassword, setMasterPassword } from "@actions/MasterPasswordActions"; + +export interface State { + /** Whether or not the user has authenticated with the master password, + * decrypting their wallets. */ + readonly isAuthed?: boolean; + + /** The master password used to encrypt and decrypt local storage data. */ + readonly masterPassword?: string; + + /** Secure random string that is encrypted with the master password to create + * the "tester" string. */ + readonly salt?: string; + /** The `salt` encrypted with the master password, to test the password is + * correct. */ + readonly tester?: string; + + /** Whether or not the user has configured and saved a master password + * before (whether or not salt+tester are present in local storage). */ + readonly hasMasterPassword?: boolean; +} + +export function getInitialMasterPasswordState(): State { + // Salt and tester from local storage (or undefined) + const salt = localStorage.getItem("salt") || undefined; + const tester = localStorage.getItem("tester") || undefined; + + // There is a master password configured if both `salt` and `tester` exist + const hasMasterPassword = !!salt && !!tester; + + return { + isAuthed: false, + + salt, + tester, + + hasMasterPassword + }; +} + +export const MasterPasswordReducer = createReducer({} as State) + .handleAction(authMasterPassword, (state: State, action: ActionType) => ({ + ...state, + isAuthed: true, + masterPassword: action.payload.password + })) + .handleAction(setMasterPassword, (state: State, action: ActionType) => ({ + ...state, + isAuthed: true, + masterPassword: action.payload.password, + salt: action.payload.salt, + tester: action.payload.tester, + hasMasterPassword: true + })); diff --git a/src/store/reducers/RootReducer.ts b/src/store/reducers/RootReducer.ts index 0919019..516fc35 100644 --- a/src/store/reducers/RootReducer.ts +++ b/src/store/reducers/RootReducer.ts @@ -3,14 +3,14 @@ // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt import { combineReducers } from "redux"; -import { WalletManagerReducer } from "./WalletManagerReducer"; +import { MasterPasswordReducer } from "./MasterPasswordReducer"; import { WalletsReducer } from "./WalletsReducer"; import { SettingsReducer } from "./SettingsReducer"; import { WebsocketReducer } from "./WebsocketReducer"; import { NodeReducer } from "./NodeReducer"; export default combineReducers({ - walletManager: WalletManagerReducer, + masterPassword: MasterPasswordReducer, wallets: WalletsReducer, settings: SettingsReducer, websocket: WebsocketReducer, diff --git a/src/store/reducers/WalletManagerReducer.ts b/src/store/reducers/WalletManagerReducer.ts deleted file mode 100644 index 178eebb..0000000 --- a/src/store/reducers/WalletManagerReducer.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { createReducer, ActionType } from "typesafe-actions"; -import { authMasterPassword, setMasterPassword } from "@actions/WalletManagerActions"; - -export interface State { - /** Whether or not the user has authenticated with the master password, - * decrypting their wallets. */ - readonly isAuthed?: boolean; - - /** The master password used to encrypt and decrypt local storage data. */ - readonly masterPassword?: string; - - /** Secure random string that is encrypted with the master password to create - * the "tester" string. */ - readonly salt?: string; - /** The `salt` encrypted with the master password, to test the password is - * correct. */ - readonly tester?: string; - - /** Whether or not the user has configured and saved a master password - * before (whether or not salt+tester are present in local storage). */ - readonly hasMasterPassword?: boolean; -} - -export function getInitialWalletManagerState(): State { - // Salt and tester from local storage (or undefined) - const salt = localStorage.getItem("salt") || undefined; - const tester = localStorage.getItem("tester") || undefined; - - // There is a master password configured if both `salt` and `tester` exist - const hasMasterPassword = !!salt && !!tester; - - return { - isAuthed: false, - - salt, - tester, - - hasMasterPassword - }; -} - -export const WalletManagerReducer = createReducer({} as State) - .handleAction(authMasterPassword, (state: State, action: ActionType) => ({ - ...state, - isAuthed: true, - masterPassword: action.payload.password - })) - .handleAction(setMasterPassword, (state: State, action: ActionType) => ({ - ...state, - isAuthed: true, - masterPassword: action.payload.password, - salt: action.payload.salt, - tester: action.payload.tester, - hasMasterPassword: true - })); diff --git a/src/store/reducers/WalletsReducer.ts b/src/store/reducers/WalletsReducer.ts index 1590db2..3eef56e 100644 --- a/src/store/reducers/WalletsReducer.ts +++ b/src/store/reducers/WalletsReducer.ts @@ -4,9 +4,8 @@ import * as actions from "@actions/WalletsActions"; import { createReducer, ActionType } from "typesafe-actions"; -import { Wallet, loadWallets, WALLET_UPDATABLE_KEYS, WALLET_SYNCABLE_KEYS } from "@wallets/Wallet"; +import { Wallet, WalletMap, loadWallets, WALLET_UPDATABLE_KEYS, WALLET_SYNCABLE_KEYS } from "@wallets"; -export interface WalletMap { [key: string]: Wallet } export interface State { readonly wallets: WalletMap; }