From 2fa5c3c3b7ede62b06433ea139e25d707b206d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 13 Nov 2020 12:44:58 +0000 Subject: [PATCH] Release/v0.9.0 (#453) * Feature/wasm update (#341) * Split text and binary client apis * Very initial attempt at new serialization * Defined ser+de for Recipient and ReplySURB * Response errors * builds with changes * Working WS API + moved to separate crate * updated python examples * Fixed parsing bug * Updated go examples * Updated rust examples * formatting * Removed unused imports * dependency updates * Further dependency changes * nymsphinx exposingn framing only if not in wasm32 * Cargo lock changes before develop merge * Pending work * Actually sending and receiving websocket from rust! * more WIP * Initial wasm client + establishing shared key with gateway! * Splitting and sending a message! * WIP * WIP * Initial wasm-ification of the gateway client * Passing reconstruction result to js callback! * Initial WASM cleaning pass * Dependency pruning * Moved processing loop to received_processor + at least ack unwrappingn * Post merge fix * Kinda updated react example * Old print statement removed * Removed yarn.lock * Fixed building issue for other clients * Fixed travis test command * Updated wasm publishing instructions * Feature/instant sending (#359) * Ability to set client in vpn mode * Connection handler for mixnode * Initial vpn mode for mixes * Updated SphinxCodec to contain more metadata * Renaming * Removed handle from mixnet client and introduced forwarder * Mixnode using new forwarder * Mixnode common module containing shared packet processing * ibid. incorporated inside mixnode * New processing for gateway * Type cleanup * Wasm fix * Fixed client config * Fixed mixnode runtime issues * Formatting * Client re-using secret on 'normal' packets * Using the same key for acks * WIP * vpn key manager cleanup * wasm fix * VPN_KEY_REUSE_LIMIT moved to config * Moved AckDelayQueue to separate common crate * Key cache invalidator * Updated dashmap used in gateway * Old typo * Additional comment * Cargo fmt * Fixed tests * Sphinx update * cache ttl as config option * Cargo fmt * Feature/network monitor (#369) * WIP commit * Further into errors (WIP) * WIP commit, more stuff compiling, but now we switch to sneaky Jedrzej trick * Websocket connections starting to work * WIP * Constructing the socket stream in one go * Nicer connections. Getting topology now works. * Fixing startup message * Injecting directory uri * Injecting good mixndodes * Deleting old healthcheck * Starting to wrap gateway client * Splitting out good topology contstruction * Starting to breathe finally * Fixed layer assignment error * Starting gateway client in a better spot * Cleanup * Renamed construct() to new() * Injected channels, removed websockets * Added a MixnetListener to deal with returned packet traffic * Simplification * Renaming and commenting a few things * Renamed temp variables * Fixed variable names * Made errors a bit more explicit on message reconstruction * Added the mixmining route / object to the directory server client * Recipient is always "me", a bit easier to understand what's going on * Subbing in test nodes works * Using QA directory * Noting which directory server we're using at monitor start * Adding Debug to MixStatus so we can print it more easily * Prettification of startup messages * Notifying directory of successful packet receipt * All nodes now being tested * Now able to do timer-based test runs and detect the last node tested * Renamed mixnet_listener to notifier * Moved message chunking into its own home * Starting to pull out packet sends * Basic functionality working! * Enabled timer runs forever * License notices * Depdendency cleanup * Import cleanup * Moved to implicit tokio runtime * Removed some unnecessary data clones * Fixed monitor doing two runs at startup rather than one * dalek version updates * This should have been split but got carried away * Initial ipv6 support * Don't test outdated nodes * Added network monitor to default workspace members * Actually sending reports * Batch-sending mix status * Cargo fmt * Minor cleanup * Malformed Cargo.lock * Post-merge fixes * Defined constant in main.rs for enabling detailed reporting * Updated package versions of the system * Checking version compatibility on startup Co-authored-by: Dave Hrycyszyn * Added sphinx socks to default workspace members (#358) * Feature/version in config (#376) * Added version field to config files * Removed built dependency * Missing non-removed call to built * Corrected version on client-core (#377) * Feature/remove validator (#380) * Replacing Rust validator sketch with Cosmos-based code. * Removing old build instructions * go fmt on generated code * Removing build files for github and pi * Starting transplant surgery * Directory now running in validator, sans metrics * Updating go sum * A partly-working directory startup integrated into the nymcli app. * Removing the Swagger docs header from nymcli, it can live in server.go * Working swagger docs. * Updating test title on Swagger docs * Directory server now integrated into validator (primitive but working) * Noted location of mixmining reports database. * Bumped golang version * Removing validator cargo files. * Moving directory API init method into the nymd module * Ditching a test which is causing tooling problems - presence will go away soon anyway * Removing the validator and merging develop * Update README.md * Update README.md * Feature/upgrade command (#381) * Exposed version parsing from version checker * Ability to upgrade mixnode from 0.8.X to 0.9.0 * Ability to upgrade gateway from 0.8.X to 0.9.0 * Ability to upgrade native client from 0.8.X to 0.9.0 * Ability to upgrade socks5 client from 0.8.X to 0.9.0 * Typo * Minor renaming * Preventing upgrade if current is not a release version * Additional upgrade restrictions * Corrected version bound * Feature/mix ed25519 identity (#388) * Introduced identity keypair to mixnode * Updated upgrade 0.9.0 mix upgrade instructions * Printing failed upgrade notice when it failed * Moved printing upgrade start notice to beginning of function * Feature/double init prevention (#386) * Updated NymConfig trait making id always obligatory. Also added extra default method implementations * Preventing init on mixnodes that were initialised before * Quotes before mix id * Preventing init on gateways that were initialised before * Preventing init on native clients that were initialised before * Preventing init on socks5 clients that were initialised before * Feature/duration cleanup (#391) * Mixnode using Duration for time intervals + human readable config fields for them * Removed old comment * Native client using Duration for time intervals + human readable config fields for them * Socks5 client using Duration for time intervals + human readable config fields for them * Gateway using Duration for time intervals + human readable config fields for them * Config compatibility with 0.8.0 debug values * Fixed previous merge issue * Moving subsidiary functions down below main so we can see where thing start * Feature/fix dependabot alerts (#393) * Fixing a dependabot alert * Fixing npm audit failures in js example for webassembly client * Fixed some more npm audit problems * Explicitly handling base58 key recovery errors (#396) * Fix compiler warnings for unneeded mut (#398) * moved new_v4_with_node to test only section (#392) * moved new_v4_with_node to test only section * cargo fmt * Feature/directory server transition (#401) * Initial changes to validator client API * Updated models * GatewayRegistrationInfo constructor * Change validator topology to convert into NymTopology without failure * Mixnode registering and unregistering presence * Directory -> Validator renamings + adjustments * Updated upgrade command for mixnode * Extracted metrics part of directory client into separate library * Removed no longer needed traits * Integrated new metrics client into mixnode * Introduced the same set of changes to the gateway * Getting active topology in client core via validator client * Updated clients to get correct topology * Introduced mix mining endpoints to validator client * Network monitor using validator client * Removed directory client * Updated wasm client * Temporarily disabled the test * Checking ok status for validator client response * Updated upgrade command for clients * Allowing using old presence directory as new validator endpoint for mixnodes and gateways * Fixed tests in non-default crates * Feature/validator api update (#402) * Updated validator API routes * missing comment * Feature/wasm client fix (#399) * Disabling wasm optimization for the moment, it isn't working on my machine * Updating the React example back to a working state, 0.8.1 npm package is published. * Merging develop into bugfix * Upgrading react-scripts to fix a major vulnerability * Using metrics interval received from server (#403) * Using metrics interval received from server * Test fix * Feature/error on noninit (#404) * More graceful shutdown in case of uninitialised client/node * Performing version check on binary `run` * Feature/no run config flag (#405) * Removed `--config` parameter in `run` commands * Removed old tests that were doing disk IO * Renamed directory arguments to validator (#408) * Feature/max retry (#409) * Maximum number of allowed reconnection attempts * Requiring initial internode connection to be successful * Decreased logging level for failing to establish initial connection * Feature/controlled reinit (#410) * Allowing init with existing id but keeps existing keys * Removed unused imports * Removed debug print statement (#411) * Updated upgrade commands to set new default validator (#413) * Feature/network monitor file topology (#412) * Network monitor loading 'good' topology from files instead * Update .gitignore * Passing address of validator as an argument * Made detailed report const flag into an argument * Flag to start network requester in open proxy mode (#418) * Renamed 'sphinx-socks' to 'network-requester' (#417) * Added option to set incentives address during mix and gateway init (#419) * Updated validator topology (#420) * Bugfix/upgrade fix (#421) * Added default for incentives address when deserializing * Ability to optionally pass incentives address during upgrade * Feature/socks improvements (#423) * Changed AvailableReader from Future to Stream * comment formatting * WIP * Maximum number of bytes read per poll * More detailed socks5 printing * Split up socks5 service provider runner * Fixed closing proxy too soon on one side * Additional log information * Printing connection id in log target * Adjusted some constants * Target field in log * Removed dead code * Decreased logging level * Explicitly seperated 'Send' and 'Connect' request + data buffering * Temporarily commented out test that fails due to not understanding limitations of tokio test io builder * Fixed socks5 tests * Starting cover traffic stream under correct condition (#422) * Fixed bunch of clippy warnings (#427) * Updated default sending rates (#430) * Feature/explorer (#431) * Initial commit of the new dashboard code. * Periodically grabbing topology json * Pulling file saving out into its own module * Ignoring downloaded topology file * Moved everything public into a public folder * Refreshing the mixmining report * Mounting static files from /public * Including mixminiming report grabber * Leaving the route in place to pick up later. It's not used right now. * Removing json download from git * Ignoring topology download * Moving recurrent jobs in to a jobs module * Adding websocket dependencies * Starting to get client/server websocket functionality running. * Fixing unused imports * Separating client and server functionality a bit more cleanly * WIP to sketch out the ws client and server a bit more * Initial metrics broadcaster * Import fixup * Spawning rocket in tokio task * Removed outdated comment * removed the js file Co-authored-by: Dave * Bugfix/metrics fixes (#434) * Updated dashboard html file * Changed explorer to listen on the same port as old dashboard * Using correct field names in js file * Sending correct key in metrics * Added nym prefix to binary names (#436) * Passing validator base url as an argument (#439) * Spawning rocket as a blocking task (#440) * Update main.js (#441) * Update main.js * Changed websocket port to 1648 * Changing default validator location to make it clear we're testnet (#437) * Changing default validator location to make it clear we're on a testnet here. * Updated validator url * Updated metrics url Co-authored-by: jstuczyn * Adding keybase to allowed.list.sample (#442) * Updated nym-run gateway id (#445) * Adjusted 'fastmode' settings (#444) * Added validators to dashboard + validator and block count (#443) * Slightly friendlier upgrade argument description (#446) * Slightly friendlier upgrade argument description * Added right format examples * Explorer public folder being relative to the binary (#447) * Explorer public folder being relative to the binary * Qualifying the public mounting path Co-authored-by: Dave * Extra argument to specify metrics websocket + long attribute (#448) * Extra argument to specify metrics websocket + long attribute * Default testnet values * Fixed the URL of the websocket (#449) * Removed hardcoded 'good gateways' in favour of pseusorandom choice from the active set (#450) * Adding graceful de-registration to service * Removed incentives form url (#451) * Updated version numbers * Updated generate changelog script * Updated changelog * wasm examples fixes Co-authored-by: Dave Hrycyszyn Co-authored-by: Ethan Frey --- .gitignore | 7 + CHANGELOG.md | 112 +- Cargo.lock | 1169 +- Cargo.toml | 15 +- README.md | 5 +- clients/client-core/Cargo.toml | 10 +- clients/client-core/src/built_info.rs | 16 - .../src/client/cover_traffic_stream.rs | 12 +- clients/client-core/src/client/mix_traffic.rs | 51 +- .../action_controller.rs | 6 +- .../input_message_listener.rs | 25 +- .../acknowledgement_control/mod.rs | 26 +- .../retransmission_request_listener.rs | 14 +- .../src/client/real_messages_control/mod.rs | 48 +- .../real_traffic_stream.rs | 109 +- .../src/client/topology_control.rs | 19 +- clients/client-core/src/config/mod.rs | 268 +- clients/client-core/src/config/template.rs | 102 - clients/client-core/src/lib.rs | 1 - clients/native/Cargo.toml | 10 +- clients/native/build.rs | 19 - .../js-examples/websocket/package-lock.json | 14 +- clients/native/src/built_info.rs | 16 - clients/native/src/client/config/mod.rs | 25 +- clients/native/src/client/config/template.rs | 20 +- clients/native/src/client/mod.rs | 31 +- clients/native/src/commands/init.rs | 129 +- clients/native/src/commands/mod.rs | 13 +- clients/native/src/commands/run.rs | 58 +- clients/native/src/commands/upgrade.rs | 187 + clients/native/src/lib.rs | 1 - clients/native/src/main.rs | 7 +- clients/native/src/websocket/handler.rs | 4 +- clients/native/src/websocket/listener.rs | 5 +- .../native/websocket-requests/src/requests.rs | 2 +- .../websocket-requests/src/responses.rs | 10 +- clients/socks5/Cargo.toml | 12 +- clients/socks5/build.rs | 19 - clients/socks5/src/built_info.rs | 16 - clients/socks5/src/client/config/mod.rs | 26 +- clients/socks5/src/client/config/template.rs | 20 +- clients/socks5/src/client/mod.rs | 30 +- clients/socks5/src/commands/init.rs | 128 +- clients/socks5/src/commands/mod.rs | 13 +- clients/socks5/src/commands/run.rs | 53 +- clients/socks5/src/commands/upgrade.rs | 187 + clients/socks5/src/lib.rs | 1 - clients/socks5/src/main.rs | 7 +- clients/socks5/src/socks/client.rs | 101 +- clients/socks5/src/socks/request.rs | 7 +- clients/socks5/src/socks/server.rs | 4 +- clients/webassembly/Cargo.toml | 11 +- clients/webassembly/build.rs | 19 - clients/webassembly/js-example/index.js | 6 +- .../webassembly/js-example/package-lock.json | 407 +- .../react-example/package-lock.json | 11682 +++++++++++----- .../webassembly/react-example/package.json | 5 +- clients/webassembly/react-example/src/App.js | 4 +- clients/webassembly/src/built_info.rs | 18 - clients/webassembly/src/client/mod.rs | 45 +- clients/webassembly/src/lib.rs | 1 - .../models/src/presence/coconodes.rs | 52 - .../models/src/presence/gateways.rs | 79 - .../models/src/presence/mixnodes.rs | 71 - .../models/src/presence/mod.rs | 21 - .../models/src/presence/topology.rs | 124 - .../client-libs/directory-client/src/lib.rs | 148 - .../src/requests/health_check_get.rs | 85 - .../directory-client/src/requests/mod.rs | 45 - .../src/requests/presence_coconodes_post.rs | 99 - .../src/requests/presence_gateways_post.rs | 100 - .../src/requests/presence_mixnodes_post.rs | 100 - .../src/requests/presence_providers_post.rs | 100 - .../src/requests/presence_topology_get.rs | 248 - .../client-libs/gateway-client/src/client.rs | 17 +- .../gateway-client/src/socket_state.rs | 15 +- .../Cargo.toml | 12 +- common/client-libs/metrics-client/src/lib.rs | 74 + .../src/models}/metrics.rs | 6 + .../src/models/mod.rs} | 1 - .../src/requests/metrics_mixes_get.rs | 19 +- .../src/requests/metrics_mixes_post.rs | 40 +- .../metrics-client/src/requests/mod.rs | 3 +- .../client-libs/mixnet-client/src/client.rs | 156 + .../src/connection_manager/mod.rs | 84 +- .../src/connection_manager/reconnector.rs | 14 +- .../src/connection_manager/writer.rs | 8 +- .../mixnet-client/src/forwarder.rs | 76 + common/client-libs/mixnet-client/src/lib.rs | 272 +- .../client-libs/validator-client/Cargo.toml | 10 +- .../client-libs/validator-client/src/lib.rs | 242 +- .../validator-client/src/models/gateway.rs | 131 + .../validator-client/src/models/mixmining.rs | 19 + .../validator-client/src/models/mixnode.rs | 127 + .../validator-client/src/models}/mod.rs | 8 +- .../validator-client/src/models/node.rs | 13 +- .../validator-client/src/models/topology.rs | 74 + .../src/models/validators.rs} | 20 +- .../src/rest_requests/active_topology_get.rs | 54 + .../rest_requests/gateway_register_post.rs | 52 + .../mix_mining_batch_status_post.rs | 98 + .../rest_requests/mix_mining_status_post.rs | 91 + .../src/rest_requests/mix_register_post.rs | 52 + .../validator-client/src/rest_requests/mod.rs | 74 + .../rest_requests/node_unregister_delete.rs | 52 + .../src/rest_requests/set_reputation_patch.rs | 63 + .../src/rest_requests/topology_get.rs | 54 + common/config/src/lib.rs | 27 +- common/crypto/Cargo.toml | 6 +- .../crypto/src/asymmetric/encryption/mod.rs | 52 +- common/crypto/src/asymmetric/identity/mod.rs | 53 +- common/mixnode-common/Cargo.toml | 21 + .../src/cached_packet_processor/cache.rs | 176 + .../src/cached_packet_processor/error.rs | 72 + .../src/cached_packet_processor/mod.rs | 17 + .../src/cached_packet_processor/processor.rs | 483 + .../mixnode-common/src/lib.rs | 2 +- .../Cargo.toml | 7 +- .../nonexhaustive-delayqueue/src/lib.rs | 34 +- common/nymsphinx/Cargo.toml | 7 +- .../acknowledgements/src/surb_ack.rs | 15 +- common/nymsphinx/addressing/src/clients.rs | 20 +- .../anonymous-replies/src/reply_surb.rs | 2 +- common/nymsphinx/cover/Cargo.toml | 1 + common/nymsphinx/cover/src/lib.rs | 15 +- common/nymsphinx/forwarding/Cargo.toml | 12 + .../nymsphinx/forwarding/src/lib.rs | 2 +- common/nymsphinx/forwarding/src/packet.rs | 128 + common/nymsphinx/framing/src/codec.rs | 339 + common/nymsphinx/framing/src/lib.rs | 135 +- common/nymsphinx/framing/src/packet.rs | 165 + common/nymsphinx/params/src/lib.rs | 10 +- common/nymsphinx/params/src/packet_modes.rs | 58 + common/nymsphinx/params/src/packet_sizes.rs | 12 +- common/nymsphinx/src/lib.rs | 1 + .../src/{preparer.rs => preparer/mod.rs} | 124 +- common/nymsphinx/src/preparer/vpn_manager.rs | 154 + common/nymsphinx/src/receiver.rs | 37 +- common/nymsphinx/types/Cargo.toml | 2 +- common/socks5/ordered-buffer/src/buffer.rs | 11 +- common/socks5/ordered-buffer/src/message.rs | 2 +- common/socks5/ordered-buffer/src/sender.rs | 2 +- .../proxy-helpers/src/available_reader.rs | 111 +- .../src/connection_controller.rs | 64 +- common/socks5/proxy-helpers/src/lib.rs | 1 - .../socks5/proxy-helpers/src/proxy_runner.rs | 157 +- .../proxy-helpers/src/read_delay_loop.rs | 73 - common/socks5/requests/src/request.rs | 33 +- common/topology/src/gateway.rs | 8 +- common/topology/src/lib.rs | 108 +- common/topology/src/mix.rs | 10 +- common/version-checker/src/lib.rs | 7 +- common/wasm-utils/src/websocket/mod.rs | 6 +- explorer/Cargo.lock | 2040 +++ explorer/Cargo.toml | 21 + explorer/README.md | 15 + explorer/public/assets/css/custom.css | 62 + explorer/public/assets/js/main.js | 288 + explorer/public/assets/js/purify.js | 2 + explorer/public/assets/js/tablesorter.js | 14 + .../public/downloads}/.gitkeep | 0 explorer/public/index.html | 342 + explorer/public/paper-dashboard/CHANGELOG.md | 21 + explorer/public/paper-dashboard/LICENSE | 21 + explorer/public/paper-dashboard/README.md | 105 + .../assets/css/bootstrap.min.css | 7 + .../assets/css/bootstrap.min.css.map | 1 + .../assets/css/paper-dashboard.css | 6431 +++++++++ .../assets/css/paper-dashboard.css.map | 78 + .../assets/css/paper-dashboard.min.css | 22 + .../paper-dashboard/assets/demo/demo.css | 189 + .../paper-dashboard/assets/demo/demo.js | 408 + .../assets/fonts/nucleo-icons.eot | Bin 0 -> 31888 bytes .../assets/fonts/nucleo-icons.ttf | Bin 0 -> 31704 bytes .../assets/fonts/nucleo-icons.woff | Bin 0 -> 20032 bytes .../assets/fonts/nucleo-icons.woff2 | Bin 0 -> 17140 bytes .../paper-dashboard/assets/img/apple-icon.png | Bin 0 -> 2446 bytes .../public/paper-dashboard/assets/img/bg5.jpg | Bin 0 -> 213199 bytes .../assets/img/damir-bosnjak.jpg | Bin 0 -> 264323 bytes .../assets/img/default-avatar.png | Bin 0 -> 2864 bytes .../assets/img/faces/ayo-ogunseinde-1.jpg | Bin 0 -> 162393 bytes .../assets/img/faces/ayo-ogunseinde-2.jpg | Bin 0 -> 77236 bytes .../assets/img/faces/clem-onojeghuo-1.jpg | Bin 0 -> 224752 bytes .../assets/img/faces/clem-onojeghuo-2.jpg | Bin 0 -> 81044 bytes .../assets/img/faces/clem-onojeghuo-3.jpg | Bin 0 -> 68124 bytes .../assets/img/faces/clem-onojeghuo-4.jpg | Bin 0 -> 164278 bytes .../assets/img/faces/erik-lucatero-1.jpg | Bin 0 -> 110201 bytes .../assets/img/faces/erik-lucatero-2.jpg | Bin 0 -> 50741 bytes .../assets/img/faces/joe-gardner-1.jpg | Bin 0 -> 131727 bytes .../assets/img/faces/joe-gardner-2.jpg | Bin 0 -> 69532 bytes .../assets/img/faces/kaci-baum-1.jpg | Bin 0 -> 139024 bytes .../assets/img/faces/kaci-baum-2.jpg | Bin 0 -> 66947 bytes .../paper-dashboard/assets/img/favicon.png | Bin 0 -> 2761 bytes .../paper-dashboard/assets/img/header.jpg | Bin 0 -> 670266 bytes .../assets/img/jan-sendereks.jpg | Bin 0 -> 268442 bytes .../paper-dashboard/assets/img/logo-small.png | Bin 0 -> 60157 bytes .../paper-dashboard/assets/img/mike.jpg | Bin 0 -> 76654 bytes .../assets/js/core/bootstrap.min.js | 7 + .../assets/js/core/jquery.min.js | 4 + .../assets/js/core/popper.min.js | 4 + .../assets/js/paper-dashboard.js | 194 + .../assets/js/paper-dashboard.js.map | 1 + .../assets/js/paper-dashboard.min.js | 16 + .../assets/js/plugins/bootstrap-notify.js | 432 + .../assets/js/plugins/chartjs.min.js | 10 + .../plugins/perfect-scrollbar.jquery.min.js | 2 + .../assets/scss/paper-dashboard.scss | 53 + .../assets/scss/paper-dashboard/_alerts.scss | 74 + .../paper-dashboard/_animated-buttons.scss | 71 + .../assets/scss/paper-dashboard/_buttons.scss | 234 + .../assets/scss/paper-dashboard/_cards.scss | 140 + .../paper-dashboard/_checkboxes-radio.scss | 142 + .../scss/paper-dashboard/_dropdown.scss | 480 + .../scss/paper-dashboard/_fixed-plugin.scss | 342 + .../assets/scss/paper-dashboard/_footers.scss | 46 + .../assets/scss/paper-dashboard/_images.scss | 7 + .../assets/scss/paper-dashboard/_inputs.scss | 403 + .../assets/scss/paper-dashboard/_misc.scss | 150 + .../assets/scss/paper-dashboard/_mixins.scss | 8 + .../assets/scss/paper-dashboard/_navbar.scss | 310 + .../scss/paper-dashboard/_nucleo-outline.scss | 566 + .../scss/paper-dashboard/_page-header.scss | 85 + .../scss/paper-dashboard/_responsive.scss | 550 + .../_sidebar-and-main-panel.scss | 361 + .../assets/scss/paper-dashboard/_tables.scss | 173 + .../scss/paper-dashboard/_typography.scss | 173 + .../scss/paper-dashboard/_variables.scss | 404 + .../paper-dashboard/cards/_card-chart.scss | 48 + .../scss/paper-dashboard/cards/_card-map.scss | 3 + .../paper-dashboard/cards/_card-plain.scss | 17 + .../paper-dashboard/cards/_card-stats.scss | 48 + .../paper-dashboard/cards/_card-user.scss | 50 + .../scss/paper-dashboard/mixins/_buttons.scss | 212 + .../scss/paper-dashboard/mixins/_cards.scss | 4 + .../paper-dashboard/mixins/_dropdown.scss | 24 + .../scss/paper-dashboard/mixins/_inputs.scss | 232 + .../paper-dashboard/mixins/_page-header.scss | 7 + .../paper-dashboard/mixins/_transparency.scss | 8 + .../mixins/_vendor-prefixes.scss | 228 + .../_plugin-animate-bootstrap-notify.scss | 227 + .../plugins/_plugin-perfect-scrollbar.scss | 113 + .../paper-dashboard/docs/documentation.html | 130 + .../paper-dashboard/examples/dashboard.html | 408 + .../paper-dashboard/examples/icons.html | 696 + .../public/paper-dashboard/examples/map.html | 246 + .../examples/notifications.html | 375 + .../paper-dashboard/examples/tables.html | 486 + .../paper-dashboard/examples/typography.html | 324 + .../paper-dashboard/examples/upgrade.html | 301 + .../public/paper-dashboard/examples/user.html | 431 + explorer/public/paper-dashboard/gulpfile.js | 34 + .../public/paper-dashboard/nucleo-icons.html | 1139 ++ explorer/public/paper-dashboard/package.json | 17 + explorer/src/jobs/mixmining.rs | 21 + explorer/src/jobs/mod.rs | 20 + explorer/src/jobs/topology.rs | 21 + explorer/src/main.rs | 79 + explorer/src/utils/file.rs | 16 + explorer/src/utils/mod.rs | 1 + explorer/src/websockets/client.rs | 66 + explorer/src/websockets/mod.rs | 28 + explorer/src/websockets/server.rs | 66 + gateway/Cargo.toml | 22 +- gateway/build.rs | 19 - gateway/gateway-requests/src/types.rs | 72 +- gateway/src/built_info.rs | 16 - gateway/src/commands/init.rs | 75 +- gateway/src/commands/mod.rs | 13 +- gateway/src/commands/run.rs | 46 +- gateway/src/commands/upgrade.rs | 192 + gateway/src/config/mod.rs | 229 +- gateway/src/config/template.rs | 10 +- gateway/src/main.rs | 7 +- .../node/client_handling/clients_handler.rs | 10 +- .../websocket/connection_handler.rs | 27 +- .../client_handling/websocket/listener.rs | 6 +- gateway/src/node/mixnet_handling/mod.rs | 1 - .../receiver/connection_handler.rs | 258 +- .../node/mixnet_handling/receiver/listener.rs | 18 +- .../receiver/packet_processing.rs | 289 +- .../src/node/mixnet_handling/sender/mod.rs | 74 - gateway/src/node/mod.rs | 105 +- gateway/src/node/presence.rs | 54 + gateway/src/node/presence/mod.rs | 113 - mixnode/Cargo.lock | 2371 ---- mixnode/Cargo.toml | 25 +- mixnode/build.rs | 19 - mixnode/src/built_info.rs | 16 - mixnode/src/commands/init.rs | 92 +- mixnode/src/commands/mod.rs | 17 +- mixnode/src/commands/run.rs | 76 +- mixnode/src/commands/upgrade.rs | 228 + mixnode/src/config/mod.rs | 317 +- mixnode/src/config/persistence/pathfinder.rs | 14 +- mixnode/src/config/template.rs | 20 +- mixnode/src/main.rs | 7 +- mixnode/src/node/listener.rs | 112 - .../node/listener/connection_handler/mod.rs | 123 + .../connection_handler/packet_processing.rs | 60 + mixnode/src/node/listener/mod.rs | 52 + mixnode/src/node/metrics.rs | 53 +- mixnode/src/node/mod.rs | 183 +- mixnode/src/node/packet_forwarding.rs | 65 - mixnode/src/node/packet_processing.rs | 114 - mixnode/src/node/presence.rs | 115 +- network-monitor/Cargo.toml | 28 + network-monitor/src/chunker.rs | 74 + network-monitor/src/main.rs | 277 + network-monitor/src/monitor.rs | 65 + network-monitor/src/notifications/mod.rs | 155 + network-monitor/src/notifications/test_run.rs | 286 + .../src/notifications/test_timeout.rs | 57 + network-monitor/src/packet_sender.rs | 199 + .../src/run_info.rs | 26 +- network-monitor/src/test_packet.rs | 173 + .../src/tested_network/good_topology.rs | 33 + network-monitor/src/tested_network/mod.rs | 86 + package-lock.json | 3 + scripts/generate_changelog.sh | 2 +- scripts/systemd/nym-mixnode.service | 1 + .../Cargo.toml | 4 +- .../allowed.list.sample | 6 +- .../src/allowed_hosts.rs | 49 +- .../src/connection.rs | 18 +- .../network-requester/src/core.rs | 276 + .../src/main.rs | 24 +- .../src/websocket.rs | 0 service-providers/sphinx-socks/src/core.rs | 205 - validator/Cargo.toml | 33 - validator/README.md | 21 - validator/build.rs | 19 - validator/diesel.toml | 5 - validator/src/built_info.rs | 16 - validator/src/commands/init.rs | 58 - validator/src/commands/mod.rs | 31 - validator/src/commands/run.rs | 66 - validator/src/config/mod.rs | 263 - validator/src/config/template.rs | 81 - validator/src/main.rs | 85 - validator/src/network/mod.rs | 19 - validator/src/network/rest/capacity/mod.rs | 59 - validator/src/network/rest/mod.rs | 72 - validator/src/network/rest/models/presence.rs | 50 - .../src/network/rest/presence/conversions.rs | 135 - .../src/network/rest/presence/mixnode.rs | 38 - validator/src/network/rest/presence/mod.rs | 6 - validator/src/network/rest/presence/models.rs | 66 - .../src/network/rest/presence/topology.rs | 23 - validator/src/network/rest/routes/topology.rs | 26 - validator/src/network/rest/staking/mod.rs | 1 - validator/src/network/tendermint/mod.rs | 82 - validator/src/schema.rs | 1 - validator/src/services/mixmining/db.rs | 105 - validator/src/services/mixmining/mod.rs | 176 - validator/src/services/mixmining/models.rs | 51 - validator/src/services/mixmining/tests/mod.rs | 11 - validator/src/validator.rs | 57 - 357 files changed, 39278 insertions(+), 13268 deletions(-) delete mode 100644 clients/client-core/src/built_info.rs delete mode 100644 clients/client-core/src/config/template.rs delete mode 100644 clients/native/build.rs delete mode 100644 clients/native/src/built_info.rs create mode 100644 clients/native/src/commands/upgrade.rs delete mode 100644 clients/socks5/build.rs delete mode 100644 clients/socks5/src/built_info.rs create mode 100644 clients/socks5/src/commands/upgrade.rs delete mode 100644 clients/webassembly/build.rs delete mode 100644 clients/webassembly/src/built_info.rs delete mode 100644 common/client-libs/directory-client/models/src/presence/coconodes.rs delete mode 100644 common/client-libs/directory-client/models/src/presence/gateways.rs delete mode 100644 common/client-libs/directory-client/models/src/presence/mixnodes.rs delete mode 100644 common/client-libs/directory-client/models/src/presence/mod.rs delete mode 100644 common/client-libs/directory-client/models/src/presence/topology.rs delete mode 100644 common/client-libs/directory-client/src/lib.rs delete mode 100644 common/client-libs/directory-client/src/requests/health_check_get.rs delete mode 100644 common/client-libs/directory-client/src/requests/mod.rs delete mode 100644 common/client-libs/directory-client/src/requests/presence_coconodes_post.rs delete mode 100644 common/client-libs/directory-client/src/requests/presence_gateways_post.rs delete mode 100644 common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs delete mode 100644 common/client-libs/directory-client/src/requests/presence_providers_post.rs delete mode 100644 common/client-libs/directory-client/src/requests/presence_topology_get.rs rename common/client-libs/{directory-client => metrics-client}/Cargo.toml (56%) create mode 100644 common/client-libs/metrics-client/src/lib.rs rename common/client-libs/{directory-client/models/src => metrics-client/src/models}/metrics.rs (88%) rename common/client-libs/{directory-client/models/src/lib.rs => metrics-client/src/models/mod.rs} (97%) rename common/client-libs/{directory-client => metrics-client}/src/requests/metrics_mixes_get.rs (96%) rename common/client-libs/{directory-client => metrics-client}/src/requests/metrics_mixes_post.rs (73%) rename validator/src/network/ethereum/ethereum.rs => common/client-libs/metrics-client/src/requests/mod.rs (91%) create mode 100644 common/client-libs/mixnet-client/src/client.rs create mode 100644 common/client-libs/mixnet-client/src/forwarder.rs create mode 100644 common/client-libs/validator-client/src/models/gateway.rs create mode 100644 common/client-libs/validator-client/src/models/mixmining.rs create mode 100644 common/client-libs/validator-client/src/models/mixnode.rs rename {validator/src/network/rest/routes => common/client-libs/validator-client/src/models}/mod.rs (88%) rename clients/client-core/build.rs => common/client-libs/validator-client/src/models/node.rs (61%) create mode 100644 common/client-libs/validator-client/src/models/topology.rs rename common/client-libs/{directory-client/models/src/presence/providers.rs => validator-client/src/models/validators.rs} (66%) create mode 100644 common/client-libs/validator-client/src/rest_requests/active_topology_get.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/gateway_register_post.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/mix_mining_batch_status_post.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/mix_mining_status_post.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/mix_register_post.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/mod.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/node_unregister_delete.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/set_reputation_patch.rs create mode 100644 common/client-libs/validator-client/src/rest_requests/topology_get.rs create mode 100644 common/mixnode-common/Cargo.toml create mode 100644 common/mixnode-common/src/cached_packet_processor/cache.rs create mode 100644 common/mixnode-common/src/cached_packet_processor/error.rs create mode 100644 common/mixnode-common/src/cached_packet_processor/mod.rs create mode 100644 common/mixnode-common/src/cached_packet_processor/processor.rs rename validator/src/services/mod.rs => common/mixnode-common/src/lib.rs (94%) rename common/{client-libs/directory-client/models => nonexhaustive-delayqueue}/Cargo.toml (56%) rename clients/client-core/src/client/real_messages_control/acknowledgement_control/ack_delay_queue.rs => common/nonexhaustive-delayqueue/src/lib.rs (69%) create mode 100644 common/nymsphinx/forwarding/Cargo.toml rename validator/src/network/rest/models/mod.rs => common/nymsphinx/forwarding/src/lib.rs (97%) create mode 100644 common/nymsphinx/forwarding/src/packet.rs create mode 100644 common/nymsphinx/framing/src/codec.rs create mode 100644 common/nymsphinx/framing/src/packet.rs create mode 100644 common/nymsphinx/params/src/packet_modes.rs rename common/nymsphinx/src/{preparer.rs => preparer/mod.rs} (82%) create mode 100644 common/nymsphinx/src/preparer/vpn_manager.rs delete mode 100644 common/socks5/proxy-helpers/src/read_delay_loop.rs create mode 100644 explorer/Cargo.lock create mode 100644 explorer/Cargo.toml create mode 100644 explorer/README.md create mode 100644 explorer/public/assets/css/custom.css create mode 100644 explorer/public/assets/js/main.js create mode 100644 explorer/public/assets/js/purify.js create mode 100644 explorer/public/assets/js/tablesorter.js rename {validator/migrations => explorer/public/downloads}/.gitkeep (100%) create mode 100644 explorer/public/index.html create mode 100644 explorer/public/paper-dashboard/CHANGELOG.md create mode 100644 explorer/public/paper-dashboard/LICENSE create mode 100644 explorer/public/paper-dashboard/README.md create mode 100644 explorer/public/paper-dashboard/assets/css/bootstrap.min.css create mode 100755 explorer/public/paper-dashboard/assets/css/bootstrap.min.css.map create mode 100644 explorer/public/paper-dashboard/assets/css/paper-dashboard.css create mode 100644 explorer/public/paper-dashboard/assets/css/paper-dashboard.css.map create mode 100644 explorer/public/paper-dashboard/assets/css/paper-dashboard.min.css create mode 100644 explorer/public/paper-dashboard/assets/demo/demo.css create mode 100644 explorer/public/paper-dashboard/assets/demo/demo.js create mode 100644 explorer/public/paper-dashboard/assets/fonts/nucleo-icons.eot create mode 100644 explorer/public/paper-dashboard/assets/fonts/nucleo-icons.ttf create mode 100644 explorer/public/paper-dashboard/assets/fonts/nucleo-icons.woff create mode 100644 explorer/public/paper-dashboard/assets/fonts/nucleo-icons.woff2 create mode 100644 explorer/public/paper-dashboard/assets/img/apple-icon.png create mode 100644 explorer/public/paper-dashboard/assets/img/bg5.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/damir-bosnjak.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/default-avatar.png create mode 100644 explorer/public/paper-dashboard/assets/img/faces/ayo-ogunseinde-1.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/ayo-ogunseinde-2.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/clem-onojeghuo-1.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/clem-onojeghuo-2.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/clem-onojeghuo-3.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/clem-onojeghuo-4.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/erik-lucatero-1.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/erik-lucatero-2.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/joe-gardner-1.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/joe-gardner-2.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/kaci-baum-1.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/faces/kaci-baum-2.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/favicon.png create mode 100644 explorer/public/paper-dashboard/assets/img/header.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/jan-sendereks.jpg create mode 100644 explorer/public/paper-dashboard/assets/img/logo-small.png create mode 100644 explorer/public/paper-dashboard/assets/img/mike.jpg create mode 100644 explorer/public/paper-dashboard/assets/js/core/bootstrap.min.js create mode 100644 explorer/public/paper-dashboard/assets/js/core/jquery.min.js create mode 100644 explorer/public/paper-dashboard/assets/js/core/popper.min.js create mode 100644 explorer/public/paper-dashboard/assets/js/paper-dashboard.js create mode 100644 explorer/public/paper-dashboard/assets/js/paper-dashboard.js.map create mode 100644 explorer/public/paper-dashboard/assets/js/paper-dashboard.min.js create mode 100644 explorer/public/paper-dashboard/assets/js/plugins/bootstrap-notify.js create mode 100644 explorer/public/paper-dashboard/assets/js/plugins/chartjs.min.js create mode 100644 explorer/public/paper-dashboard/assets/js/plugins/perfect-scrollbar.jquery.min.js create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_alerts.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_animated-buttons.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_buttons.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_cards.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_checkboxes-radio.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_dropdown.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_fixed-plugin.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_footers.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_images.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_inputs.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_misc.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_mixins.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_navbar.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_nucleo-outline.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_page-header.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_responsive.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_sidebar-and-main-panel.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_tables.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_typography.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/_variables.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/cards/_card-chart.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/cards/_card-map.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/cards/_card-plain.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/cards/_card-stats.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/cards/_card-user.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/mixins/_buttons.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/mixins/_cards.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/mixins/_dropdown.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/mixins/_inputs.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/mixins/_page-header.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/mixins/_transparency.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/mixins/_vendor-prefixes.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/plugins/_plugin-animate-bootstrap-notify.scss create mode 100644 explorer/public/paper-dashboard/assets/scss/paper-dashboard/plugins/_plugin-perfect-scrollbar.scss create mode 100644 explorer/public/paper-dashboard/docs/documentation.html create mode 100644 explorer/public/paper-dashboard/examples/dashboard.html create mode 100644 explorer/public/paper-dashboard/examples/icons.html create mode 100644 explorer/public/paper-dashboard/examples/map.html create mode 100644 explorer/public/paper-dashboard/examples/notifications.html create mode 100644 explorer/public/paper-dashboard/examples/tables.html create mode 100644 explorer/public/paper-dashboard/examples/typography.html create mode 100644 explorer/public/paper-dashboard/examples/upgrade.html create mode 100644 explorer/public/paper-dashboard/examples/user.html create mode 100644 explorer/public/paper-dashboard/gulpfile.js create mode 100644 explorer/public/paper-dashboard/nucleo-icons.html create mode 100644 explorer/public/paper-dashboard/package.json create mode 100644 explorer/src/jobs/mixmining.rs create mode 100644 explorer/src/jobs/mod.rs create mode 100644 explorer/src/jobs/topology.rs create mode 100644 explorer/src/main.rs create mode 100644 explorer/src/utils/file.rs create mode 100644 explorer/src/utils/mod.rs create mode 100644 explorer/src/websockets/client.rs create mode 100644 explorer/src/websockets/mod.rs create mode 100644 explorer/src/websockets/server.rs delete mode 100644 gateway/build.rs delete mode 100644 gateway/src/built_info.rs create mode 100644 gateway/src/commands/upgrade.rs delete mode 100644 gateway/src/node/mixnet_handling/sender/mod.rs create mode 100644 gateway/src/node/presence.rs delete mode 100644 gateway/src/node/presence/mod.rs delete mode 100644 mixnode/Cargo.lock delete mode 100644 mixnode/build.rs delete mode 100644 mixnode/src/built_info.rs create mode 100644 mixnode/src/commands/upgrade.rs delete mode 100644 mixnode/src/node/listener.rs create mode 100644 mixnode/src/node/listener/connection_handler/mod.rs create mode 100644 mixnode/src/node/listener/connection_handler/packet_processing.rs create mode 100644 mixnode/src/node/listener/mod.rs delete mode 100644 mixnode/src/node/packet_forwarding.rs delete mode 100644 mixnode/src/node/packet_processing.rs create mode 100644 network-monitor/Cargo.toml create mode 100644 network-monitor/src/chunker.rs create mode 100644 network-monitor/src/main.rs create mode 100644 network-monitor/src/monitor.rs create mode 100644 network-monitor/src/notifications/mod.rs create mode 100644 network-monitor/src/notifications/test_run.rs create mode 100644 network-monitor/src/notifications/test_timeout.rs create mode 100644 network-monitor/src/packet_sender.rs rename common/topology/src/coco.rs => network-monitor/src/run_info.rs (54%) create mode 100644 network-monitor/src/test_packet.rs create mode 100644 network-monitor/src/tested_network/good_topology.rs create mode 100644 network-monitor/src/tested_network/mod.rs create mode 100644 package-lock.json rename service-providers/{sphinx-socks => network-requester}/Cargo.toml (94%) rename service-providers/{sphinx-socks => network-requester}/allowed.list.sample (80%) rename service-providers/{sphinx-socks => network-requester}/src/allowed_hosts.rs (89%) rename service-providers/{sphinx-socks => network-requester}/src/connection.rs (77%) create mode 100644 service-providers/network-requester/src/core.rs rename service-providers/{sphinx-socks => network-requester}/src/main.rs (50%) rename service-providers/{sphinx-socks => network-requester}/src/websocket.rs (100%) delete mode 100644 service-providers/sphinx-socks/src/core.rs delete mode 100644 validator/Cargo.toml delete mode 100644 validator/README.md delete mode 100644 validator/build.rs delete mode 100644 validator/diesel.toml delete mode 100644 validator/src/built_info.rs delete mode 100644 validator/src/commands/init.rs delete mode 100644 validator/src/commands/mod.rs delete mode 100644 validator/src/commands/run.rs delete mode 100644 validator/src/config/mod.rs delete mode 100644 validator/src/config/template.rs delete mode 100644 validator/src/main.rs delete mode 100644 validator/src/network/mod.rs delete mode 100644 validator/src/network/rest/capacity/mod.rs delete mode 100644 validator/src/network/rest/mod.rs delete mode 100644 validator/src/network/rest/models/presence.rs delete mode 100644 validator/src/network/rest/presence/conversions.rs delete mode 100644 validator/src/network/rest/presence/mixnode.rs delete mode 100644 validator/src/network/rest/presence/mod.rs delete mode 100644 validator/src/network/rest/presence/models.rs delete mode 100644 validator/src/network/rest/presence/topology.rs delete mode 100644 validator/src/network/rest/routes/topology.rs delete mode 100644 validator/src/network/rest/staking/mod.rs delete mode 100644 validator/src/network/tendermint/mod.rs delete mode 100644 validator/src/schema.rs delete mode 100644 validator/src/services/mixmining/db.rs delete mode 100644 validator/src/services/mixmining/mod.rs delete mode 100644 validator/src/services/mixmining/models.rs delete mode 100644 validator/src/services/mixmining/tests/mod.rs delete mode 100644 validator/src/validator.rs diff --git a/.gitignore b/.gitignore index 4675987263..b5590ff857 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,10 @@ scripts/run_mix.sh scripts/start_local_tmux_network.sh /.floo /.flooignore +qa-v4-topology.json +qa-v6-topology.json +v4-topology.json +v6-topology.json +/explorer/downloads/topology.json +/explorer/public/downloads/mixmining.json +/explorer/public/downloads/topology.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b257ef59..9991a2b674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,113 @@ # Changelog +## [v0.9.0](https://github.com/nymtech/nym/tree/v0.9.0) (2020-11-13) + +[Full Changelog](https://github.com/nymtech/nym/compare/v0.8.1...v0.9.0) + +The main features of this release are: + +* a reputation tracking system which starts to link node reputation to quality of service +* a new component, the `nym-network-monitor`, which tracks whether nodes are working properly and providing good service +* automatic node registration and de-registration at node startup +* working Cosmos validators with a `nym` token +* starting to decentralize the old directory server into the validators +* a new block explorer at https://testnet-explorer.nymtech.net which looks the same as the old dashboard but is the basis of something much more advanced. It can be run by anyone. +* de-coupling metrics collection from directory services to make the system scale better overall +* reliability and performance improvements for mixnode networking + +See the changelog for detailed release notes. + +**Implemented enhancements:** + +- Nicer error if trying to run an uninitialised client/node [\#389](https://github.com/nymtech/nym/issues/389) +- Gateway announcement [\#383](https://github.com/nymtech/nym/issues/383) +- Add init flag for incentives address [\#382](https://github.com/nymtech/nym/issues/382) +- Ed25519 Identity Keys for Mixnodes [\#379](https://github.com/nymtech/nym/issues/379) +- Introduce version field to config files [\#375](https://github.com/nymtech/nym/issues/375) +- Change `init` to not blow away existing keys \(if exist\) [\#368](https://github.com/nymtech/nym/issues/368) +- Introduce an explicit `upgrade` command [\#367](https://github.com/nymtech/nym/issues/367) +- Show remote hostname in socks5 connection messages [\#365](https://github.com/nymtech/nym/issues/365) +- Make all `const` duration values more explicit. [\#333](https://github.com/nymtech/nym/issues/333) +- Feature/socks improvements [\#423](https://github.com/nymtech/nym/pull/423) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/instant sending [\#359](https://github.com/nymtech/nym/pull/359) ([jstuczyn](https://github.com/jstuczyn)) + +**Fixed bugs:** + +- React wasm example not compiling [\#394](https://github.com/nymtech/nym/issues/394) +- Update main.js [\#441](https://github.com/nymtech/nym/pull/441) ([jstuczyn](https://github.com/jstuczyn)) +- Bugfix/metrics fixes [\#434](https://github.com/nymtech/nym/pull/434) ([jstuczyn](https://github.com/jstuczyn)) +- Bugfix/upgrade fix [\#421](https://github.com/nymtech/nym/pull/421) ([jstuczyn](https://github.com/jstuczyn)) +- Explicitly handling base58 key recovery errors [\#396](https://github.com/nymtech/nym/pull/396) ([jstuczyn](https://github.com/jstuczyn)) +- Corrected version on client-core [\#377](https://github.com/nymtech/nym/pull/377) ([jstuczyn](https://github.com/jstuczyn)) + +**Closed issues:** + +- Make validator URL configurable [\#438](https://github.com/nymtech/nym/issues/438) +- Change default directory location [\#432](https://github.com/nymtech/nym/issues/432) +- Crank up the default bandwidth settings. [\#429](https://github.com/nymtech/nym/issues/429) +- Change "sphinx-socks" to "nym-requester" [\#428](https://github.com/nymtech/nym/issues/428) +- Clients should use only "active" nodes [\#390](https://github.com/nymtech/nym/issues/390) +- Allow persistently changing config values from command line [\#387](https://github.com/nymtech/nym/issues/387) +- Remove `--config` flag in `run` [\#385](https://github.com/nymtech/nym/issues/385) +- Metrics server should return a metrics rate value [\#374](https://github.com/nymtech/nym/issues/374) +- Integer staking [\#372](https://github.com/nymtech/nym/issues/372) +- Mixnode and gateway blockchain registration [\#371](https://github.com/nymtech/nym/issues/371) +- Remove presence notifications [\#370](https://github.com/nymtech/nym/issues/370) +- Handle invalid base58 encoding for asymmetric key recovery \(encryption and identity\) [\#285](https://github.com/nymtech/nym/issues/285) +- Socks5 nym client + bitcoin service provider [\#254](https://github.com/nymtech/nym/issues/254) +- Message reception in webassembly client [\#204](https://github.com/nymtech/nym/issues/204) +- Simplest possible staking system [\#157](https://github.com/nymtech/nym/issues/157) +- Validator should hold topology [\#77](https://github.com/nymtech/nym/issues/77) + +**Merged pull requests:** + +- Removed incentives form url [\#451](https://github.com/nymtech/nym/pull/451) ([jstuczyn](https://github.com/jstuczyn)) +- Removed hardcoded 'good gateways' in favour of pseusorandom choice fr… [\#450](https://github.com/nymtech/nym/pull/450) ([jstuczyn](https://github.com/jstuczyn)) +- Fixed the URL of the websocket [\#449](https://github.com/nymtech/nym/pull/449) ([futurechimp](https://github.com/futurechimp)) +- Extra argument to specify metrics websocket + long attribute [\#448](https://github.com/nymtech/nym/pull/448) ([jstuczyn](https://github.com/jstuczyn)) +- Explorer public folder being relative to the binary [\#447](https://github.com/nymtech/nym/pull/447) ([jstuczyn](https://github.com/jstuczyn)) +- Slightly friendlier upgrade argument description [\#446](https://github.com/nymtech/nym/pull/446) ([jstuczyn](https://github.com/jstuczyn)) +- Updated nym-run gateway id [\#445](https://github.com/nymtech/nym/pull/445) ([jstuczyn](https://github.com/jstuczyn)) +- Adjusted 'fastmode' settings [\#444](https://github.com/nymtech/nym/pull/444) ([jstuczyn](https://github.com/jstuczyn)) +- Added validators to dashboard + validator and block count [\#443](https://github.com/nymtech/nym/pull/443) ([jstuczyn](https://github.com/jstuczyn)) +- Adding keybase to allowed.list.sample [\#442](https://github.com/nymtech/nym/pull/442) ([futurechimp](https://github.com/futurechimp)) +- Spawning rocket as a blocking task [\#440](https://github.com/nymtech/nym/pull/440) ([jstuczyn](https://github.com/jstuczyn)) +- Passing validator base url as an argument [\#439](https://github.com/nymtech/nym/pull/439) ([jstuczyn](https://github.com/jstuczyn)) +- Changing default validator location to make it clear we're testnet [\#437](https://github.com/nymtech/nym/pull/437) ([futurechimp](https://github.com/futurechimp)) +- Added nym prefix to binary names [\#436](https://github.com/nymtech/nym/pull/436) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/explorer [\#431](https://github.com/nymtech/nym/pull/431) ([jstuczyn](https://github.com/jstuczyn)) +- Updated default sending rates [\#430](https://github.com/nymtech/nym/pull/430) ([jstuczyn](https://github.com/jstuczyn)) +- Fixed bunch of clippy warnings [\#427](https://github.com/nymtech/nym/pull/427) ([jstuczyn](https://github.com/jstuczyn)) +- Starting cover traffic stream under correct condition [\#422](https://github.com/nymtech/nym/pull/422) ([jstuczyn](https://github.com/jstuczyn)) +- Updated validator topology [\#420](https://github.com/nymtech/nym/pull/420) ([jstuczyn](https://github.com/jstuczyn)) +- Added option to set incentives address during mix and gateway init [\#419](https://github.com/nymtech/nym/pull/419) ([jstuczyn](https://github.com/jstuczyn)) +- Flag to start network requester in open proxy mode [\#418](https://github.com/nymtech/nym/pull/418) ([jstuczyn](https://github.com/jstuczyn)) +- Renamed 'sphinx-socks' to 'network-requester' [\#417](https://github.com/nymtech/nym/pull/417) ([jstuczyn](https://github.com/jstuczyn)) +- Updated upgrade commands to set new default validator [\#413](https://github.com/nymtech/nym/pull/413) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/network monitor file topology [\#412](https://github.com/nymtech/nym/pull/412) ([jstuczyn](https://github.com/jstuczyn)) +- Removed debug print statement [\#411](https://github.com/nymtech/nym/pull/411) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/controlled reinit [\#410](https://github.com/nymtech/nym/pull/410) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/max retry [\#409](https://github.com/nymtech/nym/pull/409) ([jstuczyn](https://github.com/jstuczyn)) +- Renamed directory arguments to validator [\#408](https://github.com/nymtech/nym/pull/408) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/no run config flag [\#405](https://github.com/nymtech/nym/pull/405) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/error on noninit [\#404](https://github.com/nymtech/nym/pull/404) ([jstuczyn](https://github.com/jstuczyn)) +- Using metrics interval received from server [\#403](https://github.com/nymtech/nym/pull/403) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/validator api update [\#402](https://github.com/nymtech/nym/pull/402) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/directory server transition [\#401](https://github.com/nymtech/nym/pull/401) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/wasm client fix [\#399](https://github.com/nymtech/nym/pull/399) ([futurechimp](https://github.com/futurechimp)) +- Fix compiler warnings for unneeded mut [\#398](https://github.com/nymtech/nym/pull/398) ([ethanfrey](https://github.com/ethanfrey)) +- Feature/fix dependabot alerts [\#393](https://github.com/nymtech/nym/pull/393) ([futurechimp](https://github.com/futurechimp)) +- moved new\_v4\_with\_node to test only section [\#392](https://github.com/nymtech/nym/pull/392) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/duration cleanup [\#391](https://github.com/nymtech/nym/pull/391) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/mix ed25519 identity [\#388](https://github.com/nymtech/nym/pull/388) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/double init prevention [\#386](https://github.com/nymtech/nym/pull/386) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/upgrade command [\#381](https://github.com/nymtech/nym/pull/381) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/remove validator [\#380](https://github.com/nymtech/nym/pull/380) ([futurechimp](https://github.com/futurechimp)) +- Feature/version in config [\#376](https://github.com/nymtech/nym/pull/376) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/network monitor [\#369](https://github.com/nymtech/nym/pull/369) ([jstuczyn](https://github.com/jstuczyn)) +- Added sphinx socks to default workspace members [\#358](https://github.com/nymtech/nym/pull/358) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/wasm update [\#341](https://github.com/nymtech/nym/pull/341) ([jstuczyn](https://github.com/jstuczyn)) + ## [v0.8.1](https://github.com/nymtech/nym/tree/v0.8.1) (2020-09-28) [Full Changelog](https://github.com/nymtech/nym/compare/v0.8.0...v0.8.1) @@ -16,7 +124,7 @@ **Merged pull requests:** -- Feature/wasm update [\#341](https://github.com/nymtech/nym/pull/341) ([jstuczyn](https://github.com/jstuczyn)) +- Hotfix/0.8.1 [\#357](https://github.com/nymtech/nym/pull/357) ([jstuczyn](https://github.com/jstuczyn)) ## [v0.8.0](https://github.com/nymtech/nym/tree/v0.8.0) (2020-09-10) @@ -332,6 +440,7 @@ This release fixes bugs in v0.5.0. All testnet node operators are advised to up **Merged pull requests:** - Bugfix/healthcheck on provided topology [\#108](https://github.com/nymtech/nym/pull/108) ([jstuczyn](https://github.com/jstuczyn)) +- Feature/check packet length [\#99](https://github.com/nymtech/nym/pull/99) ([futurechimp](https://github.com/futurechimp)) ## [v0.4.0](https://github.com/nymtech/nym/tree/v0.4.0) (2020-01-28) @@ -382,7 +491,6 @@ This release also integrates a health-checker and network topology refresh into - Feature/base58 [\#102](https://github.com/nymtech/nym/pull/102) ([futurechimp](https://github.com/futurechimp)) - Feature/panic improvements [\#101](https://github.com/nymtech/nym/pull/101) ([jstuczyn](https://github.com/jstuczyn)) - Feature/fix sphinx unwraps [\#100](https://github.com/nymtech/nym/pull/100) ([futurechimp](https://github.com/futurechimp)) -- Feature/check packet length [\#99](https://github.com/nymtech/nym/pull/99) ([futurechimp](https://github.com/futurechimp)) - Feature/version filtering improvements [\#96](https://github.com/nymtech/nym/pull/96) ([futurechimp](https://github.com/futurechimp)) - Feature/refreshing topology [\#94](https://github.com/nymtech/nym/pull/94) ([jstuczyn](https://github.com/jstuczyn)) - Feature/consistent logging [\#93](https://github.com/nymtech/nym/pull/93) ([futurechimp](https://github.com/futurechimp)) diff --git a/Cargo.lock b/Cargo.lock index dff4b74265..d2c6fa414a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,22 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "abci" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfb90d266d6ca61cea4d155dd5033c9516495f4f74f82faf1ca5c4feeb82577" -dependencies = [ - "byteorder", - "bytes 0.4.12", - "env_logger 0.7.1", - "futures 0.3.5", - "integer-encoding", - "log 0.4.11", - "protobuf", - "protobuf-codegen-pure", - "tokio 0.1.22", -] - [[package]] name = "addr2line" version = "0.13.0" @@ -164,12 +147,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.0.1" @@ -190,16 +167,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - [[package]] name = "base64" version = "0.11.0" @@ -304,34 +271,12 @@ dependencies = [ "byte-tools", ] -[[package]] -name = "bodyparser" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f023abfa58aad6f6bc4ae0630799e24d5ee0ab8bb2e49f651d9b1f9aa4f52f30" -dependencies = [ - "iron", - "persistent", - "plugin", - "serde", - "serde_json", -] - [[package]] name = "bs58" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" -[[package]] -name = "built" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa7899958f4aa3c40edc1b033d0e956763319e398924abb80a0034dda5bb198" -dependencies = [ - "cargo-lock", -] - [[package]] name = "bumpalo" version = "3.4.0" @@ -350,34 +295,12 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] - [[package]] name = "bytes" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" -[[package]] -name = "cargo-lock" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8504b63dd1249fd1745b7b4ef9b6f7b107ddeb3c95370043c7dbcc38653a2679" -dependencies = [ - "semver", - "serde", - "toml", - "url 2.1.1", -] - [[package]] name = "cc" version = "1.0.59" @@ -428,25 +351,26 @@ dependencies = [ [[package]] name = "client-core" -version = "0.8.0-dev" +version = "0.9.0" dependencies = [ - "built", "config", "crypto", - "directory-client", "dirs 2.0.2", "futures 0.3.5", "gateway-client", "gateway-requests", - "log 0.4.11", + "humantime-serde", + "log", + "nonexhaustive-delayqueue", "nymsphinx", "pemstore", - "rand 0.7.3", + "rand", "serde", "sled 0.33.0", "tempfile", - "tokio 0.2.22", + "tokio", "topology", + "validator-client", ] [[package]] @@ -534,24 +458,13 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-deque" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "maybe-uninit", -] - [[package]] name = "crossbeam-epoch" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ - "autocfg 1.0.1", + "autocfg", "cfg-if", "crossbeam-utils", "lazy_static", @@ -560,24 +473,13 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "crossbeam-queue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "maybe-uninit", -] - [[package]] name = "crossbeam-utils" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.0.1", + "autocfg", "cfg-if", "lazy_static", ] @@ -594,11 +496,11 @@ dependencies = [ "generic-array 0.14.4", "hkdf 0.9.0", "hmac 0.8.1", - "log 0.4.11", + "log", "nymsphinx-types", "pemstore", "pretty_env_logger", - "rand 0.7.3", + "rand", "stream-cipher 0.4.1", "x25519-dalek", ] @@ -650,7 +552,7 @@ checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" dependencies = [ "byteorder", "digest 0.8.1", - "rand_core 0.5.1", + "rand_core", "subtle 2.2.3", "zeroize", ] @@ -663,11 +565,20 @@ checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.5.1", + "rand_core", "subtle 2.2.3", "zeroize", ] +[[package]] +name = "dashmap" +version = "4.0.0-rc6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308a6703be2d759cb5fb7b80a23547fe73a8d5ebf70d3a4ca7f0ef4c0bfc2265" +dependencies = [ + "once_cell", +] + [[package]] name = "difference" version = "2.0.0" @@ -692,28 +603,6 @@ dependencies = [ "generic-array 0.14.4", ] -[[package]] -name = "directory-client" -version = "0.1.0" -dependencies = [ - "directory-client-models", - "log 0.4.11", - "mockito", - "pretty_env_logger", - "reqwest", - "serde", - "tokio 0.2.22", -] - -[[package]] -name = "directory-client-models" -version = "0.1.0" -dependencies = [ - "crypto", - "serde", - "topology", -] - [[package]] name = "dirs" version = "2.0.2" @@ -779,7 +668,7 @@ checksum = "53d2e93f837d749c16d118e7ddf7a4dfd0ac8f452cf51e46e9348824e5ef6851" dependencies = [ "curve25519-dalek 3.0.0", "ed25519", - "rand 0.7.3", + "rand", "serde", "sha2 0.9.1", "zeroize", @@ -801,21 +690,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" dependencies = [ "atty", - "humantime", - "log 0.4.11", - "regex", - "termcolor", -] - -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime", - "log 0.4.11", + "humantime 1.3.0", + "log", "regex", "termcolor", ] @@ -826,7 +702,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ - "version_check 0.9.2", + "version_check", ] [[package]] @@ -878,12 +754,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -1017,11 +887,11 @@ dependencies = [ "crypto", "futures 0.3.5", "gateway-requests", - "log 0.4.11", + "log", "nymsphinx", - "tokio 0.2.22", + "tokio", "tokio-tungstenite", - "tungstenite 0.11.1", + "tungstenite", "wasm-bindgen", "wasm-bindgen-futures 0.4.17", "wasm-timer", @@ -1035,13 +905,13 @@ dependencies = [ "bs58", "crypto", "futures 0.3.5", - "log 0.4.11", + "log", "nymsphinx", "pemstore", - "rand 0.7.3", + "rand", "serde", "serde_json", - "tungstenite 0.11.1", + "tungstenite", ] [[package]] @@ -1060,7 +930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -1087,7 +957,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993f9e0baeed60001cf565546b0d3dbe6a6ad23f2bd31644a133c641eccf6d53" dependencies = [ - "bytes 0.5.6", + "bytes", "fnv", "futures-core", "futures-sink", @@ -1095,7 +965,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 0.2.22", + "tokio", "tokio-util", "tracing", ] @@ -1106,7 +976,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deefd4816fb852b1ff3cb48f6c41da67be2d0e1d20b26a7a3b076da11f064b1" dependencies = [ - "log 0.4.11", + "log", "pest", "pest_derive", "quick-error 2.0.0", @@ -1175,7 +1045,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ - "bytes 0.5.6", + "bytes", "fnv", "itoa", ] @@ -1186,7 +1056,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ - "bytes 0.5.6", + "bytes", "http", ] @@ -1206,22 +1076,19 @@ dependencies = [ ] [[package]] -name = "hyper" -version = "0.10.16" +name = "humantime" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" + +[[package]] +name = "humantime-serde" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058" dependencies = [ - "base64 0.9.3", - "httparse", - "language-tags", - "log 0.3.9", - "mime 0.2.6", - "num_cpus", - "time", - "traitobject", - "typeable", - "unicase 1.4.2", - "url 1.7.2", + "humantime 2.0.1", + "serde", ] [[package]] @@ -1230,7 +1097,7 @@ version = "0.13.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e68a8dd9716185d9e64ea473ea6ef63529252e3e27623295a0378a19665d5eb" dependencies = [ - "bytes 0.5.6", + "bytes", "futures-channel", "futures-core", "futures-util", @@ -1242,7 +1109,7 @@ dependencies = [ "pin-project", "socket2", "time", - "tokio 0.2.22", + "tokio", "tower-service", "tracing", "want", @@ -1254,24 +1121,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" dependencies = [ - "bytes 0.5.6", - "hyper 0.13.7", + "bytes", + "hyper", "native-tls", - "tokio 0.2.22", + "tokio", "tokio-tls", ] -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.2.0" @@ -1289,7 +1145,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" dependencies = [ - "autocfg 1.0.1", + "autocfg", "hashbrown", ] @@ -1299,7 +1155,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" dependencies = [ - "bytes 0.5.6", + "bytes", ] [[package]] @@ -1308,12 +1164,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" -[[package]] -name = "integer-encoding" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4ebd0bd29be0f11973e9b3e219005661042a019fd757798c36a47c87852625" - [[package]] name = "iovec" version = "0.1.4" @@ -1329,22 +1179,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" -[[package]] -name = "iron" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d308ca2d884650a8bf9ed2ff4cb13fbb2207b71f64cda11dc9b892067295e8" -dependencies = [ - "hyper 0.10.16", - "log 0.3.9", - "mime_guess 1.8.8", - "modifier", - "num_cpus", - "plugin", - "typemap", - "url 1.7.2", -] - [[package]] name = "itoa" version = "0.4.6" @@ -1376,12 +1210,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -1424,15 +1252,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.11", -] - [[package]] name = "log" version = "0.4.11" @@ -1472,7 +1291,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -1482,12 +1301,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +name = "metrics-client" +version = "0.1.0" dependencies = [ - "log 0.3.9", + "mockito", + "reqwest", + "serde", + "tokio", ] [[package]] @@ -1496,26 +1316,14 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "mime_guess" -version = "1.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" -dependencies = [ - "mime 0.2.6", - "phf", - "phf_codegen", - "unicase 1.4.2", -] - [[package]] name = "mime_guess" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" dependencies = [ - "mime 0.3.16", - "unicase 2.6.0", + "mime", + "unicase", ] [[package]] @@ -1539,7 +1347,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log 0.4.11", + "log", "miow 0.2.1", "net2", "slab", @@ -1552,7 +1360,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" dependencies = [ - "log 0.4.11", + "log", "mio", "miow 0.3.5", "winapi 0.3.9", @@ -1596,12 +1404,29 @@ name = "mixnet-client" version = "0.1.0" dependencies = [ "futures 0.3.5", - "log 0.4.11", + "log", "nymsphinx", - "tokio 0.2.22", + "tokio", "tokio-util", ] +[[package]] +name = "mixnode-common" +version = "0.1.0" +dependencies = [ + "dashmap", + "futures 0.3.5", + "log", + "nonexhaustive-delayqueue", + "nymsphinx-acknowledgements", + "nymsphinx-addressing", + "nymsphinx-forwarding", + "nymsphinx-framing", + "nymsphinx-params", + "nymsphinx-types", + "tokio", +] + [[package]] name = "mockito" version = "0.23.3" @@ -1613,19 +1438,13 @@ dependencies = [ "difference", "httparse", "lazy_static", - "log 0.4.11", - "percent-encoding 2.1.0", - "rand 0.7.3", + "log", + "percent-encoding", + "rand", "regex", "serde_json", ] -[[package]] -name = "modifier" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" - [[package]] name = "native-tls" version = "0.2.4" @@ -1634,7 +1453,7 @@ checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" dependencies = [ "lazy_static", "libc", - "log 0.4.11", + "log", "openssl", "openssl-probe", "openssl-sys", @@ -1655,13 +1474,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nonexhaustive-delayqueue" +version = "0.1.0" +dependencies = [ + "tokio", +] + [[package]] name = "num-integer" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-traits", ] @@ -1671,7 +1497,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -1686,50 +1512,48 @@ dependencies = [ [[package]] name = "nym-client" -version = "0.8.1" +version = "0.9.0" dependencies = [ - "built", "clap", "client-core", "config", "crypto", - "directory-client", "dirs 3.0.1", "dotenv", "futures 0.3.5", "gateway-client", "gateway-requests", - "log 0.4.11", + "log", "nymsphinx", "pemstore", "pretty_env_logger", - "rand 0.7.3", + "rand", "serde", "serde_json", "sled 0.33.0", - "tempfile", - "tokio 0.2.22", + "tokio", "tokio-tungstenite", "topology", - "url 2.1.1", + "url", + "validator-client", + "version-checker", "websocket-requests", ] [[package]] name = "nym-client-wasm" -version = "0.8.1" +version = "0.9.0" dependencies = [ - "built", "console_error_panic_hook", "crypto", - "directory-client", "futures 0.3.5", "gateway-client", "js-sys", "nymsphinx", - "rand 0.7.3", + "rand", "serde", "topology", + "validator-client", "wasm-bindgen", "wasm-bindgen-futures 0.4.17", "wasm-bindgen-test", @@ -1739,109 +1563,130 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "0.8.1" +version = "0.9.0" dependencies = [ - "built", "clap", "config", "crypto", - "directory-client", + "dashmap", "dirs 2.0.2", "dotenv", "futures 0.3.5", "gateway-requests", - "log 0.4.11", + "humantime-serde", + "log", "mixnet-client", + "mixnode-common", "nymsphinx", "pemstore", "pretty_env_logger", - "rand 0.7.3", + "rand", "serde", "sled 0.31.0", - "tempfile", - "tokio 0.2.22", + "tokio", "tokio-tungstenite", "tokio-util", - "tungstenite 0.10.1", + "tungstenite", + "validator-client", + "version-checker", ] [[package]] name = "nym-mixnode" -version = "0.8.1" +version = "0.9.0" dependencies = [ "bs58", - "built", "clap", "config", "crypto", "curve25519-dalek 2.1.0", - "directory-client", "dirs 2.0.2", "dotenv", "futures 0.3.5", - "log 0.4.11", + "humantime-serde", + "log", + "metrics-client", "mixnet-client", + "mixnode-common", "nymsphinx", "pemstore", "pretty_env_logger", "serde", - "tempfile", - "tokio 0.2.22", + "tokio", "tokio-util", "topology", + "validator-client", + "version-checker", +] + +[[package]] +name = "nym-network-monitor" +version = "0.9.0" +dependencies = [ + "clap", + "crypto", + "dotenv", + "futures 0.3.5", + "gateway-client", + "log", + "nymsphinx", + "pretty_env_logger", + "rand", + "serde", + "serde_json", + "tokio", + "topology", + "validator-client", + "version-checker", +] + +[[package]] +name = "nym-network-requester" +version = "0.9.0" +dependencies = [ + "clap", + "dirs 2.0.2", + "futures 0.3.5", + "log", + "nymsphinx", + "ordered-buffer", + "pretty_env_logger", + "proxy-helpers", + "publicsuffix", + "rand", + "socks5-requests", + "tokio", + "tokio-tungstenite", + "websocket-requests", ] [[package]] name = "nym-socks5-client" -version = "0.8.1" +version = "0.9.0" dependencies = [ - "built", "clap", "client-core", "config", "crypto", - "directory-client", "dirs 3.0.1", "dotenv", "futures 0.3.5", "gateway-client", "gateway-requests", - "log 0.4.11", + "log", "nymsphinx", "ordered-buffer", "pin-project", "pretty_env_logger", "proxy-helpers", - "rand 0.7.3", + "rand", "serde", "snafu", "socks5-requests", - "tempfile", - "tokio 0.2.22", + "tokio", "topology", -] - -[[package]] -name = "nym-validator" -version = "0.8.0" -dependencies = [ - "abci", - "bodyparser", - "built", - "byteorder", - "clap", - "config", - "dirs 2.0.2", - "dotenv", - "futures 0.3.5", - "iron", - "log 0.4.11", - "pretty_env_logger", - "router", - "serde", - "serde_json", - "tempfile", - "tokio 0.2.22", + "validator-client", + "version-checker", ] [[package]] @@ -1854,11 +1699,13 @@ dependencies = [ "nymsphinx-anonymous-replies", "nymsphinx-chunking", "nymsphinx-cover", + "nymsphinx-forwarding", "nymsphinx-framing", "nymsphinx-params", "nymsphinx-types", - "rand 0.7.3", + "rand", "rand_distr", + "tokio", "topology", ] @@ -1871,7 +1718,7 @@ dependencies = [ "nymsphinx-params", "nymsphinx-types", "pemstore", - "rand 0.7.3", + "rand", "topology", ] @@ -1893,7 +1740,7 @@ dependencies = [ "nymsphinx-addressing", "nymsphinx-params", "nymsphinx-types", - "rand 0.7.3", + "rand", "serde", "topology", ] @@ -1902,11 +1749,11 @@ dependencies = [ name = "nymsphinx-chunking" version = "0.1.0" dependencies = [ - "log 0.4.11", + "log", "nymsphinx-addressing", "nymsphinx-params", "nymsphinx-types", - "rand 0.7.3", + "rand", ] [[package]] @@ -1917,17 +1764,27 @@ dependencies = [ "nymsphinx-acknowledgements", "nymsphinx-addressing", "nymsphinx-chunking", + "nymsphinx-forwarding", "nymsphinx-params", "nymsphinx-types", - "rand 0.7.3", + "rand", "topology", ] +[[package]] +name = "nymsphinx-forwarding" +version = "0.1.0" +dependencies = [ + "nymsphinx-addressing", + "nymsphinx-params", + "nymsphinx-types", +] + [[package]] name = "nymsphinx-framing" version = "0.1.0" dependencies = [ - "bytes 0.5.6", + "bytes", "nymsphinx-params", "nymsphinx-types", "tokio-util", @@ -1998,7 +1855,7 @@ version = "0.9.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" dependencies = [ - "autocfg 1.0.1", + "autocfg", "cc", "libc", "pkg-config", @@ -2009,18 +1866,7 @@ dependencies = [ name = "ordered-buffer" version = "0.1.0" dependencies = [ - "log 0.4.11", -] - -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.6.2", - "rustc_version", + "log", ] [[package]] @@ -2044,21 +1890,6 @@ dependencies = [ "parking_lot_core 0.8.0", ] -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if", - "cloudabi 0.0.3", - "libc", - "redox_syscall", - "rustc_version", - "smallvec 0.6.13", - "winapi 0.3.9", -] - [[package]] name = "parking_lot_core" version = "0.7.2" @@ -2069,7 +1900,7 @@ dependencies = [ "cloudabi 0.0.3", "libc", "redox_syscall", - "smallvec 1.4.2", + "smallvec", "winapi 0.3.9", ] @@ -2084,7 +1915,7 @@ dependencies = [ "instant", "libc", "redox_syscall", - "smallvec 1.4.2", + "smallvec", "winapi 0.3.9", ] @@ -2106,28 +1937,12 @@ dependencies = [ "pem", ] -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -[[package]] -name = "persistent" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8fa0009c4f3d350281309909c618abddf10bb7e3145f28410782f6a5ec74c5" -dependencies = [ - "iron", - "plugin", -] - [[package]] name = "pest" version = "2.1.3" @@ -2171,45 +1986,6 @@ dependencies = [ "sha-1 0.8.2", ] -[[package]] -name = "phf" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" -dependencies = [ - "phf_shared", - "rand 0.6.5", -] - -[[package]] -name = "phf_shared" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" -dependencies = [ - "siphasher", - "unicase 1.4.2", -] - [[package]] name = "pin-project" version = "0.4.23" @@ -2248,15 +2024,6 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" -[[package]] -name = "plugin" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" -dependencies = [ - "typemap", -] - [[package]] name = "ppv-lite86" version = "0.2.9" @@ -2270,8 +2037,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" dependencies = [ "chrono", - "env_logger 0.6.2", - "log 0.4.11", + "env_logger", + "log", ] [[package]] @@ -2284,7 +2051,7 @@ dependencies = [ "proc-macro2 1.0.20", "quote 1.0.7", "syn 1.0.40", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -2295,7 +2062,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2 1.0.20", "quote 1.0.7", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -2328,41 +2095,16 @@ dependencies = [ "unicode-xid 0.2.1", ] -[[package]] -name = "protobuf" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6563a657a014b771e7f69f06447d88d8fbb5a215ffc4cab724afb3acedcc7701" - -[[package]] -name = "protobuf-codegen" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f1bbc6db30d5d3e730b6e2326e9a64a75ca9c80d6427d6f054dc8cacc79d225" -dependencies = [ - "protobuf", -] - -[[package]] -name = "protobuf-codegen-pure" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5473ffa23d2ea3b9046764f1a22149791967aad946b6cbd99601e720afc4d0" -dependencies = [ - "protobuf", - "protobuf-codegen", -] - [[package]] name = "proxy-helpers" version = "0.1.0" dependencies = [ - "bytes 0.5.6", + "bytes", "futures 0.3.5", - "log 0.4.11", + "log", "ordered-buffer", "socks5-requests", - "tokio 0.2.22", + "tokio", "tokio-test", ] @@ -2373,11 +2115,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" dependencies = [ "error-chain", - "idna 0.2.0", + "idna", "lazy_static", "native-tls", "regex", - "url 2.1.1", + "url", ] [[package]] @@ -2410,25 +2152,6 @@ dependencies = [ "proc-macro2 1.0.20", ] -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.9", -] - [[package]] name = "rand" version = "0.7.3" @@ -2437,19 +2160,9 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", + "rand_chacha", + "rand_core", + "rand_hc", ] [[package]] @@ -2459,24 +2172,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core 0.5.1", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -2492,16 +2190,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" dependencies = [ - "rand 0.7.3", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", + "rand", ] [[package]] @@ -2510,69 +2199,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.9", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi 0.0.3", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.9", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", + "rand_core", ] [[package]] @@ -2626,52 +2253,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e" dependencies = [ "base64 0.12.3", - "bytes 0.5.6", + "bytes", "encoding_rs", "futures-core", "futures-util", "http", "http-body", - "hyper 0.13.7", + "hyper", "hyper-tls", "ipnet", "js-sys", "lazy_static", - "log 0.4.11", - "mime 0.3.16", - "mime_guess 2.0.3", + "log", + "mime", + "mime_guess", "native-tls", - "percent-encoding 2.1.0", + "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", - "tokio 0.2.22", + "tokio", "tokio-tls", - "url 2.1.1", + "url", "wasm-bindgen", "wasm-bindgen-futures 0.4.17", "web-sys", "winreg", ] -[[package]] -name = "route-recognizer" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea509065eb0b3c446acdd0102f0d46567dc30902dc0be91d6552035d92b0f4f8" - -[[package]] -name = "router" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc63b6f3b8895b0d04e816b2b1aa58fdba2d5acca3cbb8f0ab8e017347d57397" -dependencies = [ - "iron", - "route-recognizer", - "url 1.7.2", -] - [[package]] name = "rust-argon2" version = "0.8.2" @@ -2690,27 +2300,12 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "schannel" version = "0.1.19" @@ -2763,7 +2358,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ "semver-parser", - "serde", ] [[package]] @@ -2812,7 +2406,7 @@ dependencies = [ "dtoa", "itoa", "serde", - "url 2.1.1", + "url", ] [[package]] @@ -2881,12 +2475,6 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210" -[[package]] -name = "siphasher" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" - [[package]] name = "slab" version = "0.4.2" @@ -2905,7 +2493,7 @@ dependencies = [ "fs2", "fxhash", "libc", - "log 0.4.11", + "log", "parking_lot 0.10.2", ] @@ -2922,19 +2510,10 @@ dependencies = [ "fs2", "fxhash", "libc", - "log 0.4.11", + "log", "parking_lot 0.11.0", ] -[[package]] -name = "smallvec" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" -dependencies = [ - "maybe-uninit", -] - [[package]] name = "smallvec" version = "1.4.2" @@ -2986,7 +2565,7 @@ dependencies = [ [[package]] name = "sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=18aa34e1a39a5f3f14ba493ded9209658ff2cbfa#18aa34e1a39a5f3f14ba493ded9209658ff2cbfa" +source = "git+https://github.com/nymtech/sphinx?rev=283dcc77dec8ee9ed3bed58c2b878e9c18320723#283dcc77dec8ee9ed3bed58c2b878e9c18320723" dependencies = [ "aes-ctr 0.3.0", "arrayref", @@ -2994,36 +2573,16 @@ dependencies = [ "bs58", "byteorder", "chacha", - "curve25519-dalek 2.1.0", + "curve25519-dalek 3.0.0", "hkdf 0.8.0", "hmac 0.7.1", "lioness", - "log 0.4.11", - "rand 0.7.3", + "log", + "rand", "rand_distr", "sha2 0.8.2", ] -[[package]] -name = "sphinx-socks" -version = "0.8.1" -dependencies = [ - "clap", - "dirs 2.0.2", - "futures 0.3.5", - "log 0.4.11", - "nymsphinx", - "ordered-buffer", - "pretty_env_logger", - "proxy-helpers", - "publicsuffix", - "rand 0.7.3", - "socks5-requests", - "tokio 0.2.22", - "tokio-tungstenite", - "websocket-requests", -] - [[package]] name = "stream-cipher" version = "0.3.2" @@ -3103,7 +2662,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ "cfg-if", "libc", - "rand 0.7.3", + "rand", "redox_syscall", "remove_dir_all", "winapi 0.3.9", @@ -3153,33 +2712,13 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" -[[package]] -name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "mio", - "num_cpus", - "tokio-codec", - "tokio-current-thread", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", -] - [[package]] name = "tokio" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" dependencies = [ - "bytes 0.5.6", + "bytes", "fnv", "futures-core", "iovec", @@ -3197,48 +2736,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "tokio-codec" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "tokio-io", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" -dependencies = [ - "futures 0.1.29", - "tokio-executor", -] - -[[package]] -name = "tokio-executor" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" -dependencies = [ - "crossbeam-utils", - "futures 0.1.29", -] - -[[package]] -name = "tokio-io" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "log 0.4.11", -] - [[package]] name = "tokio-macros" version = "0.2.5" @@ -3250,87 +2747,15 @@ dependencies = [ "syn 1.0.40", ] -[[package]] -name = "tokio-reactor" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" -dependencies = [ - "crossbeam-utils", - "futures 0.1.29", - "lazy_static", - "log 0.4.11", - "mio", - "num_cpus", - "parking_lot 0.9.0", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - -[[package]] -name = "tokio-sync" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" -dependencies = [ - "fnv", - "futures 0.1.29", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "iovec", - "mio", - "tokio-io", - "tokio-reactor", -] - [[package]] name = "tokio-test" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0049c119b6d505c4447f5c64873636c7af6c75ab0d45fd9f618d82acb8016d" dependencies = [ - "bytes 0.5.6", + "bytes", "futures-core", - "tokio 0.2.22", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" -dependencies = [ - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils", - "futures 0.1.29", - "lazy_static", - "log 0.4.11", - "num_cpus", - "slab", - "tokio-executor", -] - -[[package]] -name = "tokio-timer" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" -dependencies = [ - "crossbeam-utils", - "futures 0.1.29", - "slab", - "tokio-executor", + "tokio", ] [[package]] @@ -3340,7 +2765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" dependencies = [ "native-tls", - "tokio 0.2.22", + "tokio", ] [[package]] @@ -3350,10 +2775,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d9e878ad426ca286e4dcae09cbd4e1973a7f8987d97570e2469703dd7f5720c" dependencies = [ "futures-util", - "log 0.4.11", + "log", "pin-project", - "tokio 0.2.22", - "tungstenite 0.11.1", + "tokio", + "tungstenite", ] [[package]] @@ -3362,12 +2787,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" dependencies = [ - "bytes 0.5.6", + "bytes", "futures-core", "futures-sink", - "log 0.4.11", + "log", "pin-project-lite", - "tokio 0.2.22", + "tokio", ] [[package]] @@ -3385,11 +2810,11 @@ version = "0.1.0" dependencies = [ "bs58", "crypto", - "log 0.4.11", + "log", "nymsphinx-addressing", "nymsphinx-types", "pretty_env_logger", - "rand 0.7.3", + "rand", "version-checker", ] @@ -3406,7 +2831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c" dependencies = [ "cfg-if", - "log 0.4.11", + "log", "tracing-core", ] @@ -3419,37 +2844,12 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - [[package]] name = "try-lock" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "tungstenite" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" -dependencies = [ - "base64 0.11.0", - "byteorder", - "bytes 0.5.6", - "http", - "httparse", - "input_buffer", - "log 0.4.11", - "rand 0.7.3", - "sha-1 0.8.2", - "url 2.1.1", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.11.1" @@ -3458,32 +2858,17 @@ checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" dependencies = [ "base64 0.12.3", "byteorder", - "bytes 0.5.6", + "bytes", "http", "httparse", "input_buffer", - "log 0.4.11", - "rand 0.7.3", + "log", + "rand", "sha-1 0.9.1", - "url 2.1.1", + "url", "utf-8", ] -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - -[[package]] -name = "typemap" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" -dependencies = [ - "unsafe-any", -] - [[package]] name = "typenum" version = "1.12.0" @@ -3496,22 +2881,13 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" -[[package]] -name = "unicase" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -dependencies = [ - "version_check 0.1.5", -] - [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.2", + "version_check", ] [[package]] @@ -3550,35 +2926,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "unsafe-any" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" -dependencies = [ - "traitobject", -] - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - [[package]] name = "url" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" dependencies = [ - "idna 0.2.0", + "idna", "matches", - "percent-encoding 2.1.0", + "percent-encoding", ] [[package]] @@ -3591,8 +2947,13 @@ checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" name = "validator-client" version = "0.1.0" dependencies = [ - "log 0.4.11", - "pretty_env_logger", + "crypto", + "log", + "mockito", + "reqwest", + "serde", + "tokio", + "topology", ] [[package]] @@ -3614,12 +2975,6 @@ dependencies = [ "semver", ] -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.2" @@ -3632,7 +2987,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.11", + "log", "try-lock", ] @@ -3668,7 +3023,7 @@ checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.11", + "log", "proc-macro2 1.0.20", "quote 1.0.7", "syn 1.0.40", @@ -3775,7 +3130,7 @@ version = "0.1.0" dependencies = [ "futures 0.3.5", "js-sys", - "tungstenite 0.11.1", + "tungstenite", "wasm-bindgen", "wasm-bindgen-futures 0.4.17", "web-sys", @@ -3876,12 +3231,12 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "0.6.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" +checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" dependencies = [ - "curve25519-dalek 2.1.0", - "rand_core 0.5.1", + "curve25519-dalek 3.0.0", + "rand_core", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index ffa921ff23..9cc49579f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,19 +13,21 @@ members = [ "clients/socks5", "clients/webassembly", "clients/client-core", - "common/client-libs/directory-client", - "common/client-libs/directory-client/models", "common/client-libs/gateway-client", + "common/client-libs/metrics-client", "common/client-libs/mixnet-client", "common/client-libs/validator-client", "common/config", "common/crypto", + "common/mixnode-common", + "common/nonexhaustive-delayqueue", "common/nymsphinx", "common/nymsphinx/acknowledgements", "common/nymsphinx/addressing", "common/nymsphinx/anonymous-replies", "common/nymsphinx/chunking", "common/nymsphinx/cover", + "common/nymsphinx/forwarding", "common/nymsphinx/framing", "common/nymsphinx/params", "common/nymsphinx/types", @@ -36,9 +38,9 @@ members = [ "common/wasm-utils", "gateway", "gateway/gateway-requests", - "service-providers/sphinx-socks", "mixnode", - "validator", + "network-monitor", + "service-providers/network-requester", ] default-members = [ @@ -46,6 +48,9 @@ default-members = [ "clients/socks5", # "clients/webassembly", "gateway", + "service-providers/network-requester", "mixnode", - "validator", + "network-monitor", ] + +exclude = ["explorer"] \ No newline at end of file diff --git a/README.md b/README.md index f104d9a3dc..08b33bddb6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ ## The Nym Privacy Platform -This repository contains the full Nym platform. +This repository contains the Nym mixnet. The platform is composed of multiple Rust crates. Top-level executable binary crates include: * nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers. * nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes. -* nym-sfw-provider - a store-and-forward service provider. The provider acts sort of like a mailbox for mixnet messages. -* nym-validator - currently just starting development. Handles consensus ordering of transactions, mixmining, and coconut credential generation and validation. +* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices. [![Build Status](https://travis-ci.com/nymtech/nym.svg?branch=develop)](https://travis-ci.com/nymtech/nym) diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 9860343c9c..505c72acbd 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -1,7 +1,6 @@ [package] -build = "build.rs" name = "client-core" -version = "0.8.0-dev" +version = "0.9.0" authors = ["Dave Hrycyszyn "] edition = "2018" @@ -10,6 +9,7 @@ edition = "2018" [dependencies] dirs = "2.0.2" futures = "0.3.1" +humantime-serde = "1.0.1" log = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0.104", features = ["derive"] } @@ -19,15 +19,13 @@ tokio = { version = "0.2", features = ["full"] } # internal config = { path = "../../common/config" } crypto = { path = "../../common/crypto" } -directory-client = { path = "../../common/client-libs/directory-client" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } +nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" } nymsphinx = { path = "../../common/nymsphinx" } pemstore = { path = "../../common/pemstore" } topology = { path = "../../common/topology" } - -[build-dependencies] -built = "0.4.3" +validator-client = { path = "../../common/client-libs/validator-client" } [dev-dependencies] tempfile = "3.1.0" \ No newline at end of file diff --git a/clients/client-core/src/built_info.rs b/clients/client-core/src/built_info.rs deleted file mode 100644 index 38b46d59e6..0000000000 --- a/clients/client-core/src/built_info.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/clients/client-core/src/client/cover_traffic_stream.rs b/clients/client-core/src/client/cover_traffic_stream.rs index 0ba912a54d..6d68046a8e 100644 --- a/clients/client-core/src/client/cover_traffic_stream.rs +++ b/clients/client-core/src/client/cover_traffic_stream.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::mix_traffic::{MixMessage, MixMessageSender}; +use crate::client::mix_traffic::BatchMixMessageSender; use crate::client::topology_control::TopologyAccessor; use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; @@ -50,7 +50,7 @@ where /// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them /// out to the network without any further delays. - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, /// Represents full address of this client. our_full_destination: Recipient, @@ -101,7 +101,7 @@ impl LoopCoverTrafficStream { average_ack_delay: time::Duration, average_packet_delay: time::Duration, average_cover_message_sending_delay: time::Duration, - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, our_full_destination: Recipient, topology_access: TopologyAccessor, ) -> Self { @@ -152,9 +152,7 @@ impl LoopCoverTrafficStream { // - we run out of memory // - the receiver channel is closed // in either case there's no recovery and we can only panic - self.mix_tx - .unbounded_send(MixMessage::new(cover_message.0, cover_message.1)) - .unwrap(); + self.mix_tx.unbounded_send(vec![cover_message]).unwrap(); // TODO: I'm not entirely sure whether this is really required, because I'm not 100% // sure how `yield_now()` works - whether it just notifies the scheduler or whether it @@ -173,7 +171,7 @@ impl LoopCoverTrafficStream { self.average_cover_message_sending_delay, )); - while let Some(_) = self.next().await { + while self.next().await.is_some() { self.on_new_message().await; } } diff --git a/clients/client-core/src/client/mix_traffic.rs b/clients/client-core/src/client/mix_traffic.rs index 1817d0dba1..1b092f95b6 100644 --- a/clients/client-core/src/client/mix_traffic.rs +++ b/clients/client-core/src/client/mix_traffic.rs @@ -16,19 +16,12 @@ use futures::channel::mpsc; use futures::StreamExt; use gateway_client::GatewayClient; use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use nymsphinx::forwarding::packet::MixPacket; use tokio::runtime::Handle; use tokio::task::JoinHandle; -pub struct MixMessage(NymNodeRoutingAddress, SphinxPacket); -pub type MixMessageSender = mpsc::UnboundedSender; -pub type MixMessageReceiver = mpsc::UnboundedReceiver; - -impl MixMessage { - pub fn new(address: NymNodeRoutingAddress, packet: SphinxPacket) -> Self { - MixMessage(address, packet) - } -} +pub type BatchMixMessageSender = mpsc::UnboundedSender>; +pub type BatchMixMessageReceiver = mpsc::UnboundedReceiver>; const MAX_FAILURE_COUNT: usize = 100; @@ -36,7 +29,7 @@ pub struct MixTrafficController { // TODO: most likely to be replaced by some higher level construct as // later on gateway_client will need to be accessible by other entities gateway_client: GatewayClient, - mix_rx: MixMessageReceiver, + mix_rx: BatchMixMessageReceiver, // TODO: this is temporary work-around. // in long run `gateway_client` will be moved away from `MixTrafficController` anyway. @@ -44,7 +37,10 @@ pub struct MixTrafficController { } impl MixTrafficController { - pub fn new(mix_rx: MixMessageReceiver, gateway_client: GatewayClient) -> MixTrafficController { + pub fn new( + mix_rx: BatchMixMessageReceiver, + gateway_client: GatewayClient, + ) -> MixTrafficController { MixTrafficController { gateway_client, mix_rx, @@ -52,31 +48,38 @@ impl MixTrafficController { } } - async fn on_message(&mut self, mix_message: MixMessage) { - debug!("Got a mix_message for {:?}", mix_message.0); - match self - .gateway_client - .send_sphinx_packet(mix_message.0, mix_message.1) - .await - { + async fn on_messages(&mut self, mut mix_packets: Vec) { + debug_assert!(!mix_packets.is_empty()); + + let success = if mix_packets.len() == 1 { + let mix_packet = mix_packets.pop().unwrap(); + self.gateway_client.send_mix_packet(mix_packet).await + } else { + self.gateway_client + .batch_send_mix_packets(mix_packets) + .await + }; + + match success { Err(e) => { - error!("Failed to send sphinx packet to the gateway! - {:?}", e); + error!("Failed to send sphinx packet(s) to the gateway! - {:?}", e); self.consecutive_gateway_failure_count += 1; if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT { - // todo: in the future this should initiate a 'graceful' shutdown + // todo: in the future this should initiate a 'graceful' shutdown or try + // to reconnect? panic!("failed to send sphinx packet to the gateway {} times in a row - assuming the gateway is dead. Can't do anything about it yet :(", MAX_FAILURE_COUNT) } } Ok(_) => { - trace!("We *might* have managed to forward sphinx packet to the gateway!"); + trace!("We *might* have managed to forward sphinx packet(s) to the gateway!"); self.consecutive_gateway_failure_count = 0; } } } pub async fn run(&mut self) { - while let Some(mix_message) = self.mix_rx.next().await { - self.on_message(mix_message).await; + while let Some(mix_packets) = self.mix_rx.next().await { + self.on_messages(mix_packets).await; } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index 5d44e00695..34e1edf5bc 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -13,10 +13,10 @@ // limitations under the License. use super::PendingAcknowledgement; -use crate::client::real_messages_control::acknowledgement_control::ack_delay_queue::AckDelayQueue; use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender; use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; use log::*; +use nonexhaustive_delayqueue::NonExhaustiveDelayQueue; use nymsphinx::chunking::fragment::FragmentIdentifier; use nymsphinx::Delay as SphinxDelay; use std::collections::HashMap; @@ -105,7 +105,7 @@ pub(super) struct ActionController { // previous version. /// DelayQueue with all `PendingAcknowledgement` that are waiting to be either received or /// retransmitted if their timer fires up. - pending_acks_timers: AckDelayQueue, + pending_acks_timers: NonExhaustiveDelayQueue, /// Channel for receiving `Action`s from other modules. incoming_actions: UnboundedReceiver, @@ -124,7 +124,7 @@ impl ActionController { ActionController { config, pending_acks_data: HashMap::new(), - pending_acks_timers: AckDelayQueue::new(), + pending_acks_timers: NonExhaustiveDelayQueue::new(), incoming_actions: receiver, retransmission_sender, }, diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index 8497662909..ff807a26ae 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -17,7 +17,7 @@ use super::PendingAcknowledgement; use crate::client::reply_key_storage::ReplyKeyStorage; use crate::client::{ inbound_messages::{InputMessage, InputMessageReceiver}, - real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender}, + real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, topology_control::TopologyAccessor, }; use futures::StreamExt; @@ -40,7 +40,7 @@ where input_receiver: InputMessageReceiver, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, topology_access: TopologyAccessor, reply_key_storage: ReplyKeyStorage, } @@ -55,7 +55,7 @@ where input_receiver: InputMessageReceiver, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, topology_access: TopologyAccessor, reply_key_storage: ReplyKeyStorage, ) -> Self { @@ -85,12 +85,13 @@ where match self .message_preparer .prepare_reply_for_use(data, reply_surb, topology, &self.ack_key) + .await { - Ok((reply_id, sphinx_packet, first_hop)) => { + Ok((mix_packet, reply_id)) => { // TODO: later probably write pending ack here // and deal with them.... // ... somehow - Some(RealMessage::new(first_hop, sphinx_packet, reply_id)) + Some(RealMessage::new(mix_packet, reply_id)) } Err(err) => { // TODO: should we have some mechanism to indicate to the user that the `reply_surb` @@ -140,18 +141,18 @@ where let prepared_fragment = self .message_preparer .prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient) + .await .unwrap(); real_messages.push(RealMessage::new( - prepared_fragment.first_hop_address, - prepared_fragment.sphinx_packet, + prepared_fragment.mix_packet, message_chunk.fragment_identifier(), )); pending_acks.push(PendingAcknowledgement::new( message_chunk, prepared_fragment.total_delay, - recipient.clone(), + recipient, )); } @@ -183,11 +184,9 @@ where }; // tells real message sender (with the poisson timer) to send this to the mix network - for real_message in real_messages { - self.real_message_sender - .unbounded_send(real_message) - .unwrap(); - } + self.real_message_sender + .unbounded_send(real_messages) + .unwrap(); } pub(super) async fn run(&mut self) { diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index 17a579e581..8f57f5bbf4 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -18,12 +18,13 @@ use self::{ retransmission_request_listener::RetransmissionRequestListener, sent_notification_listener::SentNotificationListener, }; -use super::real_traffic_stream::RealMessageSender; +use super::real_traffic_stream::BatchRealMessageSender; use crate::client::reply_key_storage::ReplyKeyStorage; use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor}; use futures::channel::mpsc; use gateway_client::AcknowledgementReceiver; use log::*; +use nymsphinx::params::PacketMode; use nymsphinx::{ acknowledgements::AckKey, addressing::clients::Recipient, @@ -38,7 +39,6 @@ use std::{ }; use tokio::task::JoinHandle; -mod ack_delay_queue; mod acknowledgement_listener; mod action_controller; mod input_message_listener; @@ -87,7 +87,7 @@ impl PendingAcknowledgement { pub(super) struct AcknowledgementControllerConnectors { /// Channel used for forwarding prepared sphinx messages into the poisson sender /// to be sent to the mix network. - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, /// Channel used for receiving raw messages from a client. The messages need to be put /// into sphinx packets first. @@ -104,7 +104,7 @@ pub(super) struct AcknowledgementControllerConnectors { impl AcknowledgementControllerConnectors { pub(super) fn new( - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, input_receiver: InputMessageReceiver, sent_notifier: SentPacketNotificationReceiver, ack_receiver: AcknowledgementReceiver, @@ -131,6 +131,14 @@ pub(super) struct Config { /// Average delay a data packet is going to get delayed at a single mixnode. average_packet_delay: Duration, + + /// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed + /// and keys reused. + packet_mode: PacketMode, + + /// If the mode of the client is set to VPN it specifies number of packets created with the + /// same initial secret until it gets rotated. + vpn_key_reuse_limit: Option, } impl Config { @@ -139,12 +147,16 @@ impl Config { ack_wait_multiplier: f64, average_ack_delay: Duration, average_packet_delay: Duration, + packet_mode: PacketMode, + vpn_key_reuse_limit: Option, ) -> Self { Config { ack_wait_addition, ack_wait_multiplier, average_ack_delay, average_packet_delay, + packet_mode, + vpn_key_reuse_limit, } } } @@ -182,9 +194,11 @@ where let message_preparer = MessagePreparer::new( rng, - ack_recipient.clone(), + ack_recipient, config.average_packet_delay, config.average_ack_delay, + config.packet_mode, + config.vpn_key_reuse_limit, ); // will listen for any acks coming from the network @@ -197,7 +211,7 @@ where // will listen for any new messages from the client let input_message_listener = InputMessageListener::new( Arc::clone(&ack_key), - ack_recipient.clone(), + ack_recipient, connectors.input_receiver, message_preparer.clone(), action_sender.clone(), diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index 92641e7e43..1e4c01dbc1 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -16,7 +16,7 @@ use super::action_controller::{Action, ActionSender}; use super::PendingAcknowledgement; use super::RetransmissionRequestReceiver; use crate::client::{ - real_messages_control::real_traffic_stream::{RealMessage, RealMessageSender}, + real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, topology_control::TopologyAccessor, }; use futures::StreamExt; @@ -35,7 +35,7 @@ where ack_recipient: Recipient, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, request_receiver: RetransmissionRequestReceiver, topology_access: TopologyAccessor, } @@ -49,7 +49,7 @@ where ack_recipient: Recipient, message_preparer: MessagePreparer, action_sender: ActionSender, - real_message_sender: RealMessageSender, + real_message_sender: BatchRealMessageSender, request_receiver: RetransmissionRequestReceiver, topology_access: TopologyAccessor, ) -> Self { @@ -94,6 +94,7 @@ where let prepared_fragment = self .message_preparer .prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, packet_recipient) + .await .unwrap(); // if we have the ONLY strong reference to the ack data, it means it was removed from the @@ -123,11 +124,10 @@ where // send to `OutQueueControl` to eventually send to the mix network self.real_message_sender - .unbounded_send(RealMessage::new( - prepared_fragment.first_hop_address, - prepared_fragment.sphinx_packet, + .unbounded_send(vec![RealMessage::new( + prepared_fragment.mix_packet, frag_id, - )) + )]) .unwrap(); } diff --git a/clients/client-core/src/client/real_messages_control/mod.rs b/clients/client-core/src/client/real_messages_control/mod.rs index a632ae8d1d..5bc8e0f216 100644 --- a/clients/client-core/src/client/real_messages_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/mod.rs @@ -22,7 +22,7 @@ use self::{ use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors; use crate::client::reply_key_storage::ReplyKeyStorage; use crate::client::{ - inbound_messages::InputMessageReceiver, mix_traffic::MixMessageSender, + inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender, topology_control::TopologyAccessor, }; use futures::channel::mpsc; @@ -30,6 +30,7 @@ use gateway_client::AcknowledgementReceiver; use log::*; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; +use nymsphinx::params::PacketMode; use rand::{rngs::OsRng, CryptoRng, Rng}; use std::sync::Arc; use std::time::Duration; @@ -41,13 +42,34 @@ mod real_traffic_stream; // TODO: ack_key and self_recipient shouldn't really be part of this config pub struct Config { + /// Key used to decrypt contents of received SURBAcks ack_key: Arc, - ack_wait_multiplier: f64, + + /// Given ack timeout in the form a * BASE_DELAY + b, it specifies the additive part `b` ack_wait_addition: Duration, + + /// Given ack timeout in the form a * BASE_DELAY + b, it specifies the multiplier `a` + ack_wait_multiplier: f64, + + /// Address of `this` client. self_recipient: Recipient, - average_packet_delay_duration: Duration, - average_ack_delay_duration: Duration, + + /// Average delay between sending subsequent packets from this client. average_message_sending_delay: Duration, + + /// Average delay a data packet is going to get delayed at a single mixnode. + average_packet_delay_duration: Duration, + + /// Average delay an acknowledgement packet is going to get delayed at a single mixnode. + average_ack_delay_duration: Duration, + + /// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed + /// and keys reused. + packet_mode: PacketMode, + + /// If the mode of the client is set to VPN it specifies number of packets created with the + /// same initial secret until it gets rotated. + vpn_key_reuse_limit: Option, } impl Config { @@ -59,6 +81,8 @@ impl Config { average_message_sending_delay: Duration, average_packet_delay_duration: Duration, self_recipient: Recipient, + packet_mode: PacketMode, + vpn_key_reuse_limit: Option, ) -> Self { Config { ack_key, @@ -68,6 +92,8 @@ impl Config { average_message_sending_delay, ack_wait_multiplier, ack_wait_addition, + packet_mode, + vpn_key_reuse_limit, } } } @@ -87,7 +113,7 @@ impl RealMessagesController { config: Config, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, - mix_sender: MixMessageSender, + mix_sender: BatchMixMessageSender, topology_access: TopologyAccessor, reply_key_storage: ReplyKeyStorage, ) -> Self { @@ -108,6 +134,8 @@ impl RealMessagesController { config.ack_wait_multiplier, config.average_ack_delay_duration, config.average_packet_delay_duration, + config.packet_mode, + config.vpn_key_reuse_limit, ); let ack_control = AcknowledgementController::new( @@ -115,7 +143,7 @@ impl RealMessagesController { rng, topology_access.clone(), Arc::clone(&config.ack_key), - config.self_recipient.clone(), + config.self_recipient, reply_key_storage, ack_controller_connectors, ); @@ -143,7 +171,7 @@ impl RealMessagesController { } } - pub(super) async fn run(&mut self) { + pub(super) async fn run(&mut self, vpn_mode: bool) { let mut out_queue_control = self.out_queue_control.take().unwrap(); let mut ack_control = self.ack_control.take().unwrap(); @@ -151,7 +179,7 @@ impl RealMessagesController { // the task to ever finish. This will of course change once we introduce // graceful shutdowns. let out_queue_control_fut = tokio::spawn(async move { - out_queue_control.run_out_queue_control().await; + out_queue_control.run_out_queue_control(vpn_mode).await; error!("The out queue controller has finished execution!"); out_queue_control }); @@ -170,9 +198,9 @@ impl RealMessagesController { // &Handle is only passed for consistency sake with other client modules, but I think // when we get to refactoring, we should apply gateway approach and make it implicit - pub fn start(mut self, handle: &Handle) -> JoinHandle { + pub fn start(mut self, handle: &Handle, vpn_mode: bool) -> JoinHandle { handle.spawn(async move { - self.run().await; + self.run(vpn_mode).await; self }) } diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs index 0626070575..f45400c720 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::client::mix_traffic::{MixMessage, MixMessageSender}; +use crate::client::mix_traffic::BatchMixMessageSender; use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender; use crate::client::topology_control::TopologyAccessor; use futures::channel::mpsc; @@ -20,12 +20,13 @@ use futures::task::{Context, Poll}; use futures::{Future, Stream, StreamExt}; use log::*; use nymsphinx::acknowledgements::AckKey; -use nymsphinx::addressing::{clients::Recipient, nodes::NymNodeRoutingAddress}; +use nymsphinx::addressing::clients::Recipient; use nymsphinx::chunking::fragment::FragmentIdentifier; use nymsphinx::cover::generate_loop_cover_packet; +use nymsphinx::forwarding::packet::MixPacket; use nymsphinx::utils::sample_poisson_duration; -use nymsphinx::SphinxPacket; use rand::{CryptoRng, Rng}; +use std::collections::VecDeque; use std::pin::Pin; use std::sync::Arc; use std::time::Duration; @@ -76,11 +77,11 @@ where /// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them /// out to the network without any further delays. - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, /// Channel used for receiving real, prepared, messages that must be first sufficiently delayed /// before being sent out into the network. - real_receiver: RealMessageReceiver, + real_receiver: BatchRealMessageReceiver, /// Represents full address of this client. our_full_destination: Recipient, @@ -90,23 +91,20 @@ where /// Accessor to the common instance of network topology. topology_access: TopologyAccessor, + + /// Buffer containing all real messages received. It is first exhausted before more are pulled. + received_buffer: VecDeque, } pub(crate) struct RealMessage { - first_hop_address: NymNodeRoutingAddress, - packet: SphinxPacket, + mix_packet: MixPacket, fragment_id: FragmentIdentifier, } impl RealMessage { - pub(crate) fn new( - first_hop_address: NymNodeRoutingAddress, - packet: SphinxPacket, - fragment_id: FragmentIdentifier, - ) -> Self { + pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self { RealMessage { - first_hop_address, - packet, + mix_packet, fragment_id, } } @@ -114,8 +112,8 @@ impl RealMessage { // messages are already prepared, etc. the real point of it is to forward it to mix_traffic // after sufficient delay -pub(crate) type RealMessageSender = mpsc::UnboundedSender; -type RealMessageReceiver = mpsc::UnboundedReceiver; +pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender>; +type BatchRealMessageReceiver = mpsc::UnboundedReceiver>; pub(crate) enum StreamMessage { Cover, @@ -145,14 +143,25 @@ where let next = now + next_poisson_delay; self.next_delay.reset(next); + // check if we have anything immediately available + if let Some(real_available) = self.received_buffer.pop_front() { + return Poll::Ready(Some(StreamMessage::Real(real_available))); + } + // decide what kind of message to send match Pin::new(&mut self.real_receiver).poll_next(cx) { // in the case our real message channel stream was closed, we should also indicate we are closed // (and whoever is using the stream should panic) Poll::Ready(None) => Poll::Ready(None), - // if there's an actual message - return it - Poll::Ready(Some(real_message)) => Poll::Ready(Some(StreamMessage::Real(real_message))), + // if there are more messages available, return first one and store the rest + Poll::Ready(Some(real_messages)) => { + self.received_buffer = real_messages.into(); + // we MUST HAVE received at least ONE message + Poll::Ready(Some(StreamMessage::Real( + self.received_buffer.pop_front().unwrap(), + ))) + } // otherwise construct a dummy one Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)), @@ -168,8 +177,8 @@ where config: Config, ack_key: Arc, sent_notifier: SentPacketNotificationSender, - mix_tx: MixMessageSender, - real_receiver: RealMessageReceiver, + mix_tx: BatchMixMessageSender, + real_receiver: BatchRealMessageReceiver, rng: R, our_full_destination: Recipient, topology_access: TopologyAccessor, @@ -184,9 +193,18 @@ where our_full_destination, rng, topology_access, + received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly } } + fn sent_notify(&self, frag_id: FragmentIdentifier) { + // well technically the message was not sent just yet, but now it's up to internal + // queues and client load rather than the required delay. So realistically we can treat + // whatever is about to happen as negligible additional delay. + trace!("{} is about to get sent to the mixnet", frag_id); + self.sent_notifier.unbounded_send(frag_id).unwrap(); + } + async fn on_message(&mut self, next_message: StreamMessage) { trace!("created new message"); @@ -209,7 +227,7 @@ where } let topology_ref = topology_ref_option.unwrap(); - let cover_message = generate_loop_cover_packet( + generate_loop_cover_packet( &mut self.rng, topology_ref, &*self.ack_key, @@ -217,22 +235,11 @@ where self.config.average_ack_delay, self.config.average_packet_delay, ) - .expect("Somehow failed to generate a loop cover message with a valid topology"); - - MixMessage::new(cover_message.0, cover_message.1) + .expect("Somehow failed to generate a loop cover message with a valid topology") } StreamMessage::Real(real_message) => { - // well technically the message was not sent just yet, but now it's up to internal - // queues and client load rather than the required delay. So realistically we can treat - // whatever is about to happen as negligible additional delay. - trace!( - "{} is about to get sent to the mixnet", - real_message.fragment_id - ); - self.sent_notifier - .unbounded_send(real_message.fragment_id) - .unwrap(); - MixMessage::new(real_message.first_hop_address, real_message.packet) + self.sent_notify(real_message.fragment_id); + real_message.mix_packet } }; @@ -240,7 +247,7 @@ where // - we run out of memory // - the receiver channel is closed // in either case there's no recovery and we can only panic - self.mix_tx.unbounded_send(next_message).unwrap(); + self.mix_tx.unbounded_send(vec![next_message]).unwrap(); // JS: Not entirely sure why or how it fixes stuff, but without the yield call, // the UnboundedReceiver [of mix_rx] will not get a chance to read anything @@ -250,16 +257,42 @@ where tokio::task::yield_now().await; } - pub(crate) async fn run_out_queue_control(&mut self) { + async fn on_batch_received(&mut self, real_messages: Vec) { + let mut mix_packets = Vec::with_capacity(real_messages.len()); + for real_message in real_messages.into_iter() { + self.sent_notify(real_message.fragment_id); + mix_packets.push(real_message.mix_packet); + } + self.mix_tx.unbounded_send(mix_packets).unwrap(); + } + + // Send messages at certain rate and if no real traffic is available, send cover message. + async fn run_normal_out_queue(&mut self) { // we should set initial delay only when we actually start the stream self.next_delay = time::delay_for(sample_poisson_duration( &mut self.rng, self.config.average_message_sending_delay, )); - debug!("Starting out queue controller..."); while let Some(next_message) = self.next().await { self.on_message(next_message).await; } } + + // Send real message as soon as it's available and don't inject ANY cover traffic. + async fn run_vpn_out_queue(&mut self) { + while let Some(next_messages) = self.real_receiver.next().await { + self.on_batch_received(next_messages).await + } + } + + pub(crate) async fn run_out_queue_control(&mut self, vpn_mode: bool) { + if vpn_mode { + debug!("Starting out queue controller in vpn mode..."); + self.run_vpn_out_queue().await + } else { + debug!("Starting out queue controller..."); + self.run_normal_out_queue().await + } + } } diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs index b39297bc17..df87eb7d6e 100644 --- a/clients/client-core/src/client/topology_control.rs +++ b/clients/client-core/src/client/topology_control.rs @@ -12,12 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::built_info; -use directory_client::DirectoryClient; use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::params::DEFAULT_NUM_MIX_HOPS; -use std::convert::TryInto; use std::ops::Deref; use std::sync::Arc; use std::time; @@ -147,7 +144,7 @@ impl TopologyRefresherConfig { } pub struct TopologyRefresher { - directory_client: directory_client::Client, + validator_client: validator_client::Client, topology_accessor: TopologyAccessor, refresh_rate: Duration, } @@ -157,25 +154,25 @@ impl TopologyRefresher { cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor, ) -> Self { - let directory_client_config = directory_client::Config::new(cfg.directory_server); - let directory_client = directory_client::Client::new(directory_client_config); + let validator_client_config = validator_client::Config::new(cfg.directory_server); + let validator_client = validator_client::Client::new(validator_client_config); TopologyRefresher { - directory_client, + validator_client, topology_accessor, refresh_rate: cfg.refresh_rate, } } async fn get_current_compatible_topology(&self) -> Option { - match self.directory_client.get_topology().await { + match self.validator_client.get_active_topology().await { Err(err) => { - error!("failed to get network topology! - {:?}", err); + error!("failed to get active network topology! - {:?}", err); None } Ok(topology) => { - let nym_topology: NymTopology = topology.try_into().ok()?; - Some(nym_topology.filter_system_version(built_info::PKG_VERSION)) + let nym_topology: NymTopology = topology.into(); + Some(nym_topology.filter_system_version(env!("CARGO_PKG_VERSION"))) } } } diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index b14cdc26ae..2ce486cb62 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -13,27 +13,85 @@ // limitations under the License. use config::NymConfig; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{self, IntoDeserializer, Visitor}, + Deserialize, Deserializer, Serialize, +}; use std::marker::PhantomData; use std::path::PathBuf; -use std::time; +use std::time::Duration; pub mod persistence; +pub const MISSING_VALUE: &str = "MISSING VALUE"; + // 'CLIENT' -const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net"; +pub const DEFAULT_VALIDATOR_REST_ENDPOINT: &str = "http://testnet-validator1.nymtech.net:8081"; + // 'DEBUG' -// where applicable, the below are defined in milliseconds const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; -// all delays are in milliseconds -const DEFAULT_ACK_WAIT_ADDITION: u64 = 800; -const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: u64 = 1000; -const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: u64 = 100; -const DEFAULT_AVERAGE_PACKET_DELAY: u64 = 100; -const DEFAULT_TOPOLOGY_REFRESH_RATE: u64 = 30_000; -const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: u64 = 5_000; -const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: u64 = 1_500; +const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500); +const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200); +const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20); +const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50); +const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_millis(30_000); +const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000); +const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000; + +const ZERO_DELAY: Duration = Duration::from_nanos(0); + +// custom function is defined to deserialize based on whether field contains a pre 0.9.0 +// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 +// +// TODO: when we get to refactoring down the line, this code can just be removed +// and all Duration fields could just have #[serde(with = "humantime_serde")] instead +// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, +// for argument sake, 0.11.0 out +fn deserialize_duration<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct DurationVisitor; + + impl<'de> Visitor<'de> for DurationVisitor { + type Value = Duration; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("u64 or a duration") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + self.visit_u64(value as u64) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(Duration::from_millis(Deserialize::deserialize( + value.into_deserializer(), + )?)) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + humantime_serde::deserialize(value.into_deserializer()) + } + } + + deserializer.deserialize_any(DurationVisitor) +} + +pub fn missing_string_value() -> String { + MISSING_VALUE.to_string() +} #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] @@ -118,14 +176,26 @@ impl Config { self.client.gateway_listener = gateway_listener.into(); } - pub fn with_custom_directory>(&mut self, directory_server: S) { - self.client.directory_server = directory_server.into(); + pub fn set_custom_validator>(&mut self, validator: S) { + self.client.validator_rest_url = validator.into(); } pub fn set_high_default_traffic_volume(&mut self) { - self.debug.average_packet_delay = 10; - self.debug.loop_cover_traffic_average_delay = 20; // 50 cover messages / s - self.debug.message_sending_average_delay = 5; // 200 "real" messages / s + self.debug.average_packet_delay = Duration::from_millis(1); + self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2000000); // basically don't really send cover messages + self.debug.message_sending_average_delay = Duration::from_millis(4); // 250 "real" messages / s + } + + pub fn set_vpn_mode(&mut self, vpn_mode: bool) { + self.client.vpn_mode = vpn_mode; + } + + pub fn set_vpn_key_reuse_limit(&mut self, reuse_limit: usize) { + self.debug.vpn_key_reuse_limit = Some(reuse_limit) + } + + pub fn set_custom_version(&mut self, version: &str) { + self.client.version = version.to_string(); } pub fn get_id(&self) -> String { @@ -164,8 +234,8 @@ impl Config { self.client.ack_key_file.clone() } - pub fn get_directory_server(&self) -> String { - self.client.directory_server.clone() + pub fn get_validator_rest_endpoint(&self) -> String { + self.client.validator_rest_url.clone() } pub fn get_gateway_id(&self) -> String { @@ -177,40 +247,71 @@ impl Config { } // Debug getters - pub fn get_average_packet_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.average_packet_delay) + pub fn get_average_packet_delay(&self) -> Duration { + if self.client.vpn_mode { + ZERO_DELAY + } else { + self.debug.average_packet_delay + } } - pub fn get_average_ack_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.average_ack_delay) + pub fn get_average_ack_delay(&self) -> Duration { + if self.client.vpn_mode { + ZERO_DELAY + } else { + self.debug.average_ack_delay + } } pub fn get_ack_wait_multiplier(&self) -> f64 { self.debug.ack_wait_multiplier } - pub fn get_ack_wait_addition(&self) -> time::Duration { - time::Duration::from_millis(self.debug.ack_wait_addition) + pub fn get_ack_wait_addition(&self) -> Duration { + self.debug.ack_wait_addition } - pub fn get_loop_cover_traffic_average_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.loop_cover_traffic_average_delay) + pub fn get_loop_cover_traffic_average_delay(&self) -> Duration { + self.debug.loop_cover_traffic_average_delay } - pub fn get_message_sending_average_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.message_sending_average_delay) + pub fn get_message_sending_average_delay(&self) -> Duration { + if self.client.vpn_mode { + ZERO_DELAY + } else { + self.debug.message_sending_average_delay + } } - pub fn get_gateway_response_timeout(&self) -> time::Duration { - time::Duration::from_millis(self.debug.gateway_response_timeout) + pub fn get_gateway_response_timeout(&self) -> Duration { + self.debug.gateway_response_timeout } - pub fn get_topology_refresh_rate(&self) -> time::Duration { - time::Duration::from_millis(self.debug.topology_refresh_rate) + pub fn get_topology_refresh_rate(&self) -> Duration { + self.debug.topology_refresh_rate } - pub fn get_topology_resolution_timeout(&self) -> time::Duration { - time::Duration::from_millis(self.debug.topology_resolution_timeout) + pub fn get_topology_resolution_timeout(&self) -> Duration { + self.debug.topology_resolution_timeout + } + + pub fn get_vpn_mode(&self) -> bool { + self.client.vpn_mode + } + + pub fn get_vpn_key_reuse_limit(&self) -> Option { + match self.get_vpn_mode() { + false => None, + true => Some( + self.debug + .vpn_key_reuse_limit + .unwrap_or_else(|| DEFAULT_VPN_KEY_REUSE_LIMIT), + ), + } + } + + pub fn get_version(&self) -> &str { + &self.client.version } } @@ -225,13 +326,23 @@ impl Default for Config { } #[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] pub struct Client { + /// Version of the client for which this configuration was created. + #[serde(default = "missing_string_value")] + version: String, + /// ID specifies the human readable ID of this particular client. id: String, - /// URL to the directory server. - directory_server: String, + /// URL to the validator server for obtaining network topology. + #[serde(default = "missing_string_value")] + validator_rest_url: String, + + /// Special mode of the system such that all messages are sent as soon as they are received + /// and no cover traffic is generated. If set all message delays are set to 0 and overwriting + /// 'Debug' values will have no effect. + #[serde(default)] + vpn_mode: bool, /// Path to file containing private identity key. private_identity_key_file: PathBuf, @@ -269,15 +380,17 @@ pub struct Client { nym_root_directory: PathBuf, #[serde(skip)] - super_struct: PhantomData, + super_struct: PhantomData<*const T>, } impl Default for Client { fn default() -> Self { // there must be explicit checks for whether id is not empty later Client { + version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), - directory_server: DEFAULT_DIRECTORY_SERVER.to_string(), + validator_rest_url: DEFAULT_VALIDATOR_REST_ENDPOINT.to_string(), + vpn_mode: false, private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_encryption_key_file: Default::default(), @@ -295,31 +408,31 @@ impl Default for Client { impl Client { fn default_private_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("private_identity.pem") + T::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("public_identity.pem") + T::default_data_directory(id).join("public_identity.pem") } fn default_private_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("private_encryption.pem") + T::default_data_directory(id).join("private_encryption.pem") } fn default_public_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("public_encryption.pem") + T::default_data_directory(id).join("public_encryption.pem") } fn default_gateway_shared_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("gateway_shared.pem") + T::default_data_directory(id).join("gateway_shared.pem") } fn default_ack_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("ack_key.pem") + T::default_data_directory(id).join("ack_key.pem") } fn default_reply_encryption_key_store_path(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("reply_key_store") + T::default_data_directory(id).join("reply_key_store") } } @@ -340,15 +453,21 @@ pub struct Debug { /// sent packet is going to be delayed at any given mix node. /// So for a packet going through three mix nodes, on average, it will take three times this value /// until the packet reaches its destination. - /// The provided value is interpreted as milliseconds. - average_packet_delay: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + average_packet_delay: Duration, /// The parameter of Poisson distribution determining how long, on average, /// sent acknowledgement is going to be delayed at any given mix node. /// So for an ack going through three mix nodes, on average, it will take three times this value /// until the packet reaches its destination. - /// The provided value is interpreted as milliseconds. - average_ack_delay: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + average_ack_delay: Duration, /// Value multiplied with the expected round trip time of an acknowledgement packet before /// it is assumed it was lost and retransmission of the data packet happens. @@ -358,36 +477,58 @@ pub struct Debug { /// Value added to the expected round trip time of an acknowledgement packet before /// it is assumed it was lost and retransmission of the data packet happens. /// In an ideal network with 0 latency, this value would have been 0. - /// The provided value is interpreted as milliseconds. - ack_wait_addition: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + ack_wait_addition: Duration, /// The parameter of Poisson distribution determining how long, on average, /// it is going to take for another loop cover traffic message to be sent. - /// The provided value is interpreted as milliseconds. - loop_cover_traffic_average_delay: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + loop_cover_traffic_average_delay: Duration, /// The parameter of Poisson distribution determining how long, on average, /// it is going to take another 'real traffic stream' message to be sent. /// If no real packets are available and cover traffic is enabled, /// a loop cover message is sent instead in order to preserve the rate. - /// The provided value is interpreted as milliseconds. - message_sending_average_delay: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + message_sending_average_delay: Duration, /// How long we're willing to wait for a response to a message sent to the gateway, /// before giving up on it. - /// The provided value is interpreted as milliseconds. - gateway_response_timeout: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + gateway_response_timeout: Duration, /// The uniform delay every which clients are querying the directory server /// to try to obtain a compatible network topology to send sphinx packets through. - /// The provided value is interpreted as milliseconds. - topology_refresh_rate: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + topology_refresh_rate: Duration, /// During topology refresh, test packets are sent through every single possible network /// path. This timeout determines waiting period until it is decided that the packet /// did not reach its destination. - /// The provided value is interpreted as milliseconds. - topology_resolution_timeout: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + topology_resolution_timeout: Duration, + + /// If the mode of the client is set to VPN it specifies number of packets created with the + /// same initial secret until it gets rotated. + vpn_key_reuse_limit: Option, } impl Default for Debug { @@ -402,6 +543,7 @@ impl Default for Debug { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, + vpn_key_reuse_limit: None, } } } diff --git a/clients/client-core/src/config/template.rs b/clients/client-core/src/config/template.rs deleted file mode 100644 index fbe0f404c2..0000000000 --- a/clients/client-core/src/config/template.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub(crate) fn config_template() -> &'static str { - // While using normal toml marshalling would have been way simpler with less overhead, - // I think it's useful to have comments attached to the saved config file to explain behaviour of - // particular fields. - // Note: any changes to the template must be reflected in the appropriate structs in mod.rs. - r#" -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -##### main base client config options ##### - -[client] -# Human readable ID of this particular client. -id = '{{ client.id }}' - -# URL to the directory server. -directory_server = '{{ client.directory_server }}' - -# Path to file containing private identity key. -private_identity_key_file = '{{ client.private_identity_key_file }}' - -# Path to file containing public identity key. -public_identity_key_file = '{{ client.public_identity_key_file }}' - -# Path to file containing private encryption key. -private_encryption_key_file = '{{ client.private_encryption_key_file }}' - -# Path to file containing public encryption key. -public_encryption_key_file = '{{ client.public_encryption_key_file }}' - -# Full path to file containing reply encryption keys of all reply-SURBs we have ever -# sent but not received back. -reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}' - -##### additional client config options ##### - -# ID of the gateway from which the client should be fetching messages. -gateway_id = '{{ client.gateway_id }}' - -# Address of the gateway listener to which all client requests should be sent. -gateway_listener = '{{ client.gateway_listener }}' - -# A gateway specific, optional, base58 stringified shared key used for -# communication with particular gateway. -gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' - -# Path to file containing key used for encrypting and decrypting the content of an -# acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ client.ack_key_file }}' - -##### advanced configuration options ##### - -# Absolute path to the home Nym Clients directory. -nym_root_directory = '{{ client.nym_root_directory }}' - - -##### socket config options ##### - -[socket] - -# allowed values are 'WebSocket' or 'None' -socket_type = '{{ socket.socket_type }}' - -# if applicable (for the case of 'WebSocket'), the port on which the client -# will be listening for incoming requests -listening_port = {{ socket.listening_port }} - - -##### logging configuration options ##### - -[logging] - -# TODO - - -##### debug configuration options ##### -# The following options should not be modified unless you know EXACTLY what you are doing -# as if set incorrectly, they may impact your anonymity. - -[debug] - -average_packet_delay = {{ debug.average_packet_delay }} -average_ack_delay = {{ debug.average_ack_delay }} -loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} -message_sending_average_delay = {{ debug.message_sending_average_delay }} - -"# -} diff --git a/clients/client-core/src/lib.rs b/clients/client-core/src/lib.rs index a557c4a3b9..9dfb96a5d0 100644 --- a/clients/client-core/src/lib.rs +++ b/clients/client-core/src/lib.rs @@ -1,3 +1,2 @@ -pub mod built_info; pub mod client; pub mod config; diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 0f5086c908..602fabdfb8 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,7 +1,6 @@ [package] -build = "build.rs" name = "nym-client" -version = "0.8.1" +version = "0.9.0" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] edition = "2018" @@ -34,17 +33,14 @@ tokio-tungstenite = "0.11.0" # websocket client-core = { path = "../client-core" } config = { path = "../../common/config" } crypto = { path = "../../common/crypto" } -directory-client = { path = "../../common/client-libs/directory-client" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } nymsphinx = { path = "../../common/nymsphinx" } pemstore = { path = "../../common/pemstore" } topology = { path = "../../common/topology" } websocket-requests = { path = "websocket-requests" } - -[build-dependencies] -built = "0.4.3" +validator-client = { path = "../../common/client-libs/validator-client" } +version-checker = { path = "../../common/version-checker" } [dev-dependencies] -tempfile = "3.1.0" serde_json = "1.0" # for the "textsend" example \ No newline at end of file diff --git a/clients/native/build.rs b/clients/native/build.rs deleted file mode 100644 index 56d753472c..0000000000 --- a/clients/native/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use built; - -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/clients/native/examples/js-examples/websocket/package-lock.json b/clients/native/examples/js-examples/websocket/package-lock.json index ecd7996108..7e0bc59151 100644 --- a/clients/native/examples/js-examples/websocket/package-lock.json +++ b/clients/native/examples/js-examples/websocket/package-lock.json @@ -3232,9 +3232,9 @@ } }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, "node-libs-browser": { @@ -4145,12 +4145,12 @@ "dev": true }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "dev": true, "requires": { - "node-forge": "0.9.0" + "node-forge": "^0.10.0" } }, "semver": { diff --git a/clients/native/src/built_info.rs b/clients/native/src/built_info.rs deleted file mode 100644 index 38b46d59e6..0000000000 --- a/clients/native/src/built_info.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/clients/native/src/client/config/mod.rs b/clients/native/src/client/config/mod.rs index 9de9b8ee90..e86f4aa212 100644 --- a/clients/native/src/client/config/mod.rs +++ b/clients/native/src/client/config/mod.rs @@ -14,6 +14,7 @@ use crate::client::config::template::config_template; use client_core::config::Config as BaseConfig; +pub use client_core::config::MISSING_VALUE; use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -54,10 +55,6 @@ impl NymConfig for Config { config_template() } - fn config_file_name() -> String { - "config.toml".to_string() - } - fn default_root_directory() -> PathBuf { dirs::home_dir() .expect("Failed to evaluate $HOME value") @@ -135,23 +132,3 @@ impl Default for Socket { } } } - -#[cfg(test)] -mod client_config { - use super::*; - - #[test] - fn after_saving_default_config_the_loaded_one_is_identical() { - // need to figure out how to do something similar but without touching the disk - // or the file system at all... - let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); - let default_config = Config::new("foomp".to_string()); - default_config - .save_to_file(Some(temp_location.clone())) - .unwrap(); - - let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); - - assert_eq!(default_config, loaded_config); - } -} diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index fbe0f404c2..792c2ac30b 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -24,11 +24,19 @@ pub(crate) fn config_template() -> &'static str { ##### main base client config options ##### [client] +# Version of the client for which this configuration was created. +version = '{{ client.version }}' + # Human readable ID of this particular client. id = '{{ client.id }}' -# URL to the directory server. -directory_server = '{{ client.directory_server }}' +# URL to the validator server for obtaining network topology. +validator_rest_url = '{{ client.validator_rest_url }}' + +# Special mode of the system such that all messages are sent as soon as they are received +# and no cover traffic is generated. If set all message delays are set to 0 and overwriting +# 'Debug' values will have no effect. +vpn_mode = {{ client.vpn_mode }} # Path to file containing private identity key. private_identity_key_file = '{{ client.private_identity_key_file }}' @@ -93,10 +101,10 @@ listening_port = {{ socket.listening_port }} [debug] -average_packet_delay = {{ debug.average_packet_delay }} -average_ack_delay = {{ debug.average_ack_delay }} -loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} -message_sending_average_delay = {{ debug.message_sending_average_delay }} +average_packet_delay = '{{ debug.average_packet_delay }}' +average_ack_delay = '{{ debug.average_ack_delay }}' +loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}' +message_sending_average_delay = '{{ debug.message_sending_average_delay }}' "# } diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 9931f36829..e94d9fe518 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -20,7 +20,7 @@ use client_core::client::inbound_messages::{ }; use client_core::client::key_manager::KeyManager; use client_core::client::mix_traffic::{ - MixMessageReceiver, MixMessageSender, MixTrafficController, + BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController, }; use client_core::client::real_messages_control; use client_core::client::real_messages_control::RealMessagesController; @@ -43,6 +43,7 @@ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::anonymous_replies::ReplySURB; +use nymsphinx::params::PacketMode; use nymsphinx::receiver::ReconstructedMessage; use tokio::runtime::Runtime; @@ -88,7 +89,7 @@ impl NymClient { pub fn as_mix_recipient(&self) -> Recipient { Recipient::new( *self.key_manager.identity_keypair().public_key(), - self.key_manager.encryption_keypair().public_key().clone(), + *self.key_manager.encryption_keypair().public_key(), // TODO: below only works under assumption that gateway address == gateway id // (which currently is true) NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(), @@ -100,7 +101,7 @@ impl NymClient { fn start_cover_traffic_stream( &self, topology_accessor: TopologyAccessor, - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, ) { info!("Starting loop cover traffic stream..."); // we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())" @@ -128,8 +129,14 @@ impl NymClient { reply_key_storage: ReplyKeyStorage, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, - mix_sender: MixMessageSender, + mix_sender: BatchMixMessageSender, ) { + let packet_mode = if self.config.get_base().get_vpn_mode() { + PacketMode::VPN + } else { + PacketMode::Mix + }; + let controller_config = real_messages_control::Config::new( self.key_manager.ack_key(), self.config.get_base().get_ack_wait_multiplier(), @@ -138,6 +145,8 @@ impl NymClient { self.config.get_base().get_message_sending_average_delay(), self.config.get_base().get_average_packet_delay(), self.as_mix_recipient(), + packet_mode, + self.config.get_base().get_vpn_key_reuse_limit(), ); info!("Starting real traffic stream..."); @@ -154,7 +163,8 @@ impl NymClient { reply_key_storage, ) }); - real_messages_controller.start(self.runtime.handle()); + real_messages_controller + .start(self.runtime.handle(), self.config.get_base().get_vpn_mode()); } // buffer controlling all messages fetched from provider @@ -216,7 +226,7 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_base().get_directory_server(), + self.config.get_base().get_validator_rest_endpoint(), self.config.get_base().get_topology_refresh_rate(), ); let mut topology_refresher = @@ -225,7 +235,7 @@ impl NymClient { // components depending on topology would see a non-empty view info!( "Obtaining initial network topology from {}", - self.config.get_base().get_directory_server() + self.config.get_base().get_validator_rest_endpoint() ); self.runtime.block_on(topology_refresher.refresh()); @@ -250,7 +260,7 @@ impl NymClient { // requests? fn start_mix_traffic_controller( &mut self, - mix_rx: MixMessageReceiver, + mix_rx: BatchMixMessageReceiver, gateway_client: GatewayClient, ) { info!("Starting mix traffic controller..."); @@ -378,7 +388,10 @@ impl NymClient { input_receiver, sphinx_message_sender.clone(), ); - self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender); + + if !self.config.get_base().get_vpn_mode() { + self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender); + } match self.config.get_socket_type() { SocketType::WebSocket => { diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 56aeb628a1..2a17b38c76 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::built_info; use crate::client::config::Config; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; @@ -20,21 +19,14 @@ use client_core::client::key_manager::KeyManager; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use config::NymConfig; use crypto::asymmetric::identity; -use directory_client::DirectoryClient; use gateway_client::GatewayClient; use gateway_requests::registration::handshake::SharedKeys; use rand::rngs::OsRng; use rand::seq::SliceRandom; -use std::convert::TryInto; use std::sync::Arc; use std::time::Duration; use topology::{gateway, NymTopology}; -const GOOD_GATEWAYS: [&str; 2] = [ - "D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr", - "5nrYxPR8gt2Gzo2BbHtsGf66KAEQY91WmM1eW78EphNy", -]; - pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("init") .about("Initialise a Nym client. Do this first!") @@ -49,9 +41,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Id of the gateway we are going to connect to.") .takes_value(true) ) - .arg(Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the client is getting topology from") + .arg(Arg::with_name("validator") + .long("validator") + .help("Address of the validator server the client is getting topology from") .takes_value(true), ) .arg(Arg::with_name("disable-socket") @@ -64,6 +56,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Port for the socket (if applicable) to listen on in all subsequent runs") .takes_value(true) ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("fastmode") .long("fastmode") .hidden(true) // this will prevent this flag from being displayed in `--help` @@ -92,28 +95,30 @@ async fn register_with_gateway( .expect("failed to register with the gateway!") } -async fn gateway_details(directory_server: &str, gateway_id: &str) -> gateway::Node { - let directory_client_config = directory_client::Config::new(directory_server.to_string()); - let directory_client = directory_client::Client::new(directory_client_config); - let topology = directory_client.get_topology().await.unwrap(); - let nym_topology: NymTopology = topology.try_into().expect("Invalid topology data!"); - let version_filtered_topology = nym_topology.filter_system_version(built_info::PKG_VERSION); +async fn gateway_details(validator_server: &str, chosen_gateway_id: Option<&str>) -> gateway::Node { + let validator_client_config = validator_client::Config::new(validator_server.to_string()); + let validator_client = validator_client::Client::new(validator_client_config); + let topology = validator_client.get_active_topology().await.unwrap(); + let nym_topology: NymTopology = topology.into(); + let version_filtered_topology = nym_topology.filter_system_version(env!("CARGO_PKG_VERSION")); - version_filtered_topology - .gateways() - .iter() - .find(|gateway| gateway.identity_key.to_base58_string() == gateway_id) - .expect(&*format!("no gateway with id {} exists!", gateway_id)) - .clone() -} + // if we have chosen particular gateway - use it, otherwise choose a random one. + // (remember that in active topology all gateways have at least 100 reputation so should + // be working correctly) -fn select_gateway(arg: Option<&str>) -> &str { - if let Some(gateway_id) = arg { - gateway_id + if let Some(gateway_id) = chosen_gateway_id { + version_filtered_topology + .gateways() + .iter() + .find(|gateway| gateway.identity_key.to_base58_string() == gateway_id) + .expect(&*format!("no gateway with id {} exists!", gateway_id)) + .clone() } else { - // TODO1: this should only be done on testnet - // TODO2: it should probably check if chosen gateway is actually online - GOOD_GATEWAYS.choose(&mut rand::thread_rng()).unwrap() + version_filtered_topology + .gateways() + .choose(&mut rand::thread_rng()) + .expect("there are no gateways on the network!") + .clone() } } @@ -121,7 +126,16 @@ pub fn execute(matches: &ArgMatches) { println!("Initialising client..."); let id = matches.value_of("id").unwrap(); // required for now + + let already_init = if Config::default_config_file_path(id).exists() { + println!("Client \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id); + true + } else { + false + }; + let mut config = Config::new(id); + let mut rng = OsRng; // TODO: ideally that should be the last thing that's being done to config. @@ -131,33 +145,42 @@ pub fn execute(matches: &ArgMatches) { config.get_base_mut().set_high_default_traffic_volume(); } - // create identity, encryption and ack keys. - let mut key_manager = KeyManager::new(&mut rng); + // if client was already initialised, don't generate new keys, not re-register with gateway + // (because this would create new shared key) + if !already_init { + // create identity, encryption and ack keys. + let mut key_manager = KeyManager::new(&mut rng); - let gateway_id = select_gateway(matches.value_of("gateway")); - config.get_base_mut().with_gateway_id(gateway_id); + let chosen_gateway_id = matches.value_of("gateway"); - let registration_fut = async { - let gate_details = - gateway_details(&config.get_base().get_directory_server(), gateway_id).await; - let shared_keys = - register_with_gateway(&gate_details, key_manager.identity_keypair()).await; - (shared_keys, gate_details.client_listener) - }; + let registration_fut = async { + let gate_details = gateway_details( + &config.get_base().get_validator_rest_endpoint(), + chosen_gateway_id, + ) + .await; + config + .get_base_mut() + .with_gateway_id(gate_details.identity_key.to_base58_string()); + let shared_keys = + register_with_gateway(&gate_details, key_manager.identity_keypair()).await; + (shared_keys, gate_details.client_listener) + }; - // TODO: is there perhaps a way to make it work without having to spawn entire runtime? - let mut rt = tokio::runtime::Runtime::new().unwrap(); - let (shared_keys, gateway_listener) = rt.block_on(registration_fut); - config - .get_base_mut() - .with_gateway_listener(gateway_listener); - key_manager.insert_gateway_shared_key(shared_keys); + // TODO: is there perhaps a way to make it work without having to spawn entire runtime? + let mut rt = tokio::runtime::Runtime::new().unwrap(); + let (shared_keys, gateway_listener) = rt.block_on(registration_fut); + config + .get_base_mut() + .with_gateway_listener(gateway_listener); + key_manager.insert_gateway_shared_key(shared_keys); - let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); - key_manager - .store_keys(&pathfinder) - .expect("Failed to generated keys"); - println!("Saved all generated keys"); + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); + key_manager + .store_keys(&pathfinder) + .expect("Failed to generated keys"); + println!("Saved all generated keys"); + } let config_save_location = config.get_config_file_save_location(); config diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index ceb9c81aea..6523653c36 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -15,12 +15,13 @@ use crate::client::config::{Config, SocketType}; use clap::ArgMatches; -pub mod init; -pub mod run; +pub(crate) mod init; +pub(crate) mod run; +pub(crate) mod upgrade; pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { - if let Some(directory) = matches.value_of("directory") { - config.get_base_mut().with_custom_directory(directory); + if let Some(validator) = matches.value_of("validator") { + config.get_base_mut().set_custom_validator(validator); } if let Some(gateway_id) = matches.value_of("gateway") { @@ -31,6 +32,10 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_socket(SocketType::None); } + if matches.is_present("vpn-mode") { + config.get_base_mut().set_vpn_mode(true); + } + if let Some(port) = matches.value_of("port").map(|port| port.parse::()) { if let Err(err) = port { // if port was overridden, it must be parsable diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index eb0486f918..0d08129e8c 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -17,6 +17,8 @@ use crate::client::NymClient; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; use config::NymConfig; +use log::*; +use version_checker::is_minor_version_compatible; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("run") @@ -28,14 +30,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .required(true) ) // the rest of arguments are optional, they are used to override settings in config file - .arg(Arg::with_name("config") - .long("config") - .help("Custom path to the nym-mixnet-client configuration file") - .takes_value(true) - ) - .arg(Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the client is getting topology from") + .arg(Arg::with_name("validator") + .long("validator") + .help("Address of the validator server the client is getting topology from") .takes_value(true), ) .arg(Arg::with_name("gateway") @@ -47,6 +44,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .long("disable-socket") .help("Whether to not start the websocket") ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("port") .short("p") .long("port") @@ -55,14 +63,42 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { ) } +// this only checks compatibility between config the binary. It does not take into consideration +// network version. It might do so in the future. +fn version_check(cfg: &Config) -> bool { + let binary_version = env!("CARGO_PKG_VERSION"); + let config_version = cfg.get_base().get_version(); + if binary_version != config_version { + warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version); + if is_minor_version_compatible(binary_version, config_version) { + info!("but they are still semver compatible. However, consider running the `upgrade` command"); + true + } else { + error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); + false + } + } else { + true + } +} + pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); - let mut config = - Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id)) - .expect("Failed to load config file"); + let mut config = match Config::load_from_file(id) { + Ok(cfg) => cfg, + Err(err) => { + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err); + return; + } + }; config = override_config(config, matches); + if !version_check(&config) { + error!("failed the local version check"); + return; + } + NymClient::new(config).run_forever(); } diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs new file mode 100644 index 0000000000..4ea784c6b2 --- /dev/null +++ b/clients/native/src/commands/upgrade.rs @@ -0,0 +1,187 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client::config::{Config, MISSING_VALUE}; +use clap::{App, Arg, ArgMatches}; +use client_core::config::DEFAULT_VALIDATOR_REST_ENDPOINT; +use config::NymConfig; +use std::fmt::Display; +use std::process; +use version_checker::{parse_version, Version}; + +fn print_start_upgrade(from: D1, to: D2) { + println!( + "\n==================\nTrying to upgrade mixnode from {} to {} ...", + from, to + ); +} + +fn print_failed_upgrade(from: D1, to: D2) { + eprintln!( + "Upgrade from {} to {} failed!\n==================\n", + from, to + ); +} + +fn print_successful_upgrade(from: D1, to: D2) { + println!( + "Upgrade from {} to {} was successful!\n==================\n", + from, to + ); +} + +fn pre_090_upgrade(from: &str, mut config: Config) -> Config { + // this is not extracted to separate function as you only have to manually pass version + // if upgrading from pre090 version + let from = match from.strip_prefix("v") { + Some(stripped) => stripped, + None => from, + }; + + let from = match from.strip_prefix("V") { + Some(stripped) => stripped, + None => from, + }; + + let from_version = parse_version(from).expect("invalid version provided!"); + if from_version.major == 0 && from_version.minor < 8 { + // technically this could be implemented, but is there any point in that? + eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead"); + process::exit(1) + } + + if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { + eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); + process::exit(1) + } + + // note: current is guaranteed to not have any `build` information suffix (nor pre-release + // information), as this was asserted at the beginning of this command) + // + // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate + // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) + // this way we don't need to have all the crazy paths on how to upgrade from any version to any + // other version. We just upgrade one minor version at a time. + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + let to_version = if current.major == 0 && current.minor == 9 { + current + } else { + Version::new(0, 9, 0) + }; + + if config.get_base().get_validator_rest_endpoint() != MISSING_VALUE { + eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + } + + print_start_upgrade(&from_version, &to_version); + + config + .get_base_mut() + .set_custom_version(to_version.to_string().as_ref()); + + println!( + "Setting validator REST endpoint to to {}", + DEFAULT_VALIDATOR_REST_ENDPOINT + ); + + config + .get_base_mut() + .set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + + config.save_to_file(None).unwrap_or_else(|err| { + eprintln!("failed to overwrite config file! - {:?}", err); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + }); + + print_successful_upgrade(from_version, to_version); + + config +} + +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the mixnode") + .arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-client we want to upgrade") + .takes_value(true) + .required(true), + ) + // the rest of arguments depend on the upgrade path + .arg(Arg::with_name("current version") + .long("current-version") + .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") + .takes_value(true) + ) +} + +pub fn execute(matches: &ArgMatches) { + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + + // technically this is not a correct way of checking it as a released version might contain valid build identifiers + // however, we are not using them ourselves at the moment and hence it should be fine. + // if we change our mind, we could easily tweak this code + if current.is_prerelease() || !current.build.is_empty() { + eprintln!( + "Trying to upgrade to a non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + let id = matches.value_of("id").unwrap(); + + let mut existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {:?}", err); + process::exit(1) + }); + + // versions fields were added in 0.9.0 + if existing_config.get_base().get_version() == MISSING_VALUE { + let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { + eprintln!( + "trying to upgrade from pre v0.9.0 without providing current system version!" + ); + process::exit(1) + }); + + // upgrades up to 0.9.0 + existing_config = pre_090_upgrade(self_reported_version, existing_config); + } + + let config_version = + Version::parse(existing_config.get_base().get_version()).unwrap_or_else(|err| { + eprintln!("failed to parse node version! - {:?}", err); + process::exit(1) + }); + + if config_version.is_prerelease() || !config_version.build.is_empty() { + eprintln!( + "Trying to upgrade to from non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + // here be upgrade path to 0.10.0 and beyond based on version number from config + if config_version == current { + println!("You're using the most recent version!"); + } else { + eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current); + process::exit(1) + } +} diff --git a/clients/native/src/lib.rs b/clients/native/src/lib.rs index 0934a2e435..c38ba57566 100644 --- a/clients/native/src/lib.rs +++ b/clients/native/src/lib.rs @@ -12,6 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod built_info; pub mod client; pub mod websocket; diff --git a/clients/native/src/main.rs b/clients/native/src/main.rs index 04e74a88c3..a59fd48239 100644 --- a/clients/native/src/main.rs +++ b/clients/native/src/main.rs @@ -14,7 +14,6 @@ use clap::{App, ArgMatches}; -pub mod built_info; pub mod client; pub mod commands; pub mod websocket; @@ -25,11 +24,12 @@ fn main() { println!("{}", banner()); let arg_matches = App::new("Nym Client") - .version(built_info::PKG_VERSION) + .version(env!("CARGO_PKG_VERSION")) .author("Nymtech") .about("Implementation of the Nym Client") .subcommand(commands::init::command_args()) .subcommand(commands::run::command_args()) + .subcommand(commands::upgrade::command_args()) .get_matches(); execute(arg_matches); @@ -39,6 +39,7 @@ fn execute(matches: ArgMatches) { match matches.subcommand() { ("init", Some(m)) => commands::init::execute(m), ("run", Some(m)) => commands::run::execute(m), + ("upgrade", Some(m)) => commands::upgrade::execute(m), _ => println!("{}", usage()), } } @@ -60,7 +61,7 @@ fn banner() -> String { (client - version {:}) "#, - built_info::PKG_VERSION + env!("CARGO_PKG_VERSION") ) } diff --git a/clients/native/src/websocket/handler.rs b/clients/native/src/websocket/handler.rs index f47383fe85..44fe122bac 100644 --- a/clients/native/src/websocket/handler.rs +++ b/clients/native/src/websocket/handler.rs @@ -57,7 +57,7 @@ impl Clone for Handler { Handler { msg_input: self.msg_input.clone(), buffer_requester: self.buffer_requester.clone(), - self_full_address: self.self_full_address.clone(), + self_full_address: self.self_full_address, socket: None, received_response_type: Default::default(), } @@ -112,7 +112,7 @@ impl Handler { } fn handle_self_address(&self) -> ServerResponse { - ServerResponse::SelfAddress(self.self_full_address.clone()) + ServerResponse::SelfAddress(self.self_full_address) } fn handle_request(&mut self, request: ClientRequest) -> Option { diff --git a/clients/native/src/websocket/listener.rs b/clients/native/src/websocket/listener.rs index 170fbaa52c..0d0b99ad60 100644 --- a/clients/native/src/websocket/listener.rs +++ b/clients/native/src/websocket/listener.rs @@ -28,10 +28,7 @@ enum State { impl State { fn is_connected(&self) -> bool { - match self { - State::Connected => true, - _ => false, - } + matches!(self, State::Connected) } } diff --git a/clients/native/websocket-requests/src/requests.rs b/clients/native/websocket-requests/src/requests.rs index 27159f1f1a..49bdb139e9 100644 --- a/clients/native/websocket-requests/src/requests.rs +++ b/clients/native/websocket-requests/src/requests.rs @@ -288,7 +288,7 @@ mod tests { let recipient_string = recipient.to_string(); let send_request_no_surb = ClientRequest::Send { - recipient: recipient.clone(), + recipient, message: b"foomp".to_vec(), with_reply_surb: false, }; diff --git a/clients/native/websocket-requests/src/responses.rs b/clients/native/websocket-requests/src/responses.rs index 235ddf9189..c47f8dcd99 100644 --- a/clients/native/websocket-requests/src/responses.rs +++ b/clients/native/websocket-requests/src/responses.rs @@ -310,12 +310,10 @@ impl ServerResponse { RECEIVED_RESPONSE_TAG => Self::deserialize_received(b), SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b), ERROR_RESPONSE_TAG => Self::deserialize_error(b), - n => { - return Err(error::Error::new( - ErrorKind::UnknownResponse, - format!("type {}", n), - )) - } + n => Err(error::Error::new( + ErrorKind::UnknownResponse, + format!("type {}", n), + )), } } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 9bfed2c035..8746321f35 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,7 +1,6 @@ [package] -build = "build.rs" name = "nym-socks5-client" -version = "0.8.1" +version = "0.9.0" authors = ["Dave Hrycyszyn "] edition = "2018" @@ -26,7 +25,6 @@ tokio = { version = "0.2", features = ["rt-threaded"] } client-core = { path = "../client-core" } config = { path = "../../common/config" } crypto = { path = "../../common/crypto" } -directory-client = { path = "../../common/client-libs/directory-client" } gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-requests = { path = "../../gateway/gateway-requests" } nymsphinx = { path = "../../common/nymsphinx" } @@ -34,9 +32,5 @@ ordered-buffer = {path = "../../common/socks5/ordered-buffer"} socks5-requests = { path = "../../common/socks5/requests" } topology = { path = "../../common/topology" } proxy-helpers = { path = "../../common/socks5/proxy-helpers" } - -[build-dependencies] -built = "0.4.3" - -[dev-dependencies] -tempfile = "3.1.0" \ No newline at end of file +validator-client = { path = "../../common/client-libs/validator-client" } +version-checker = { path = "../../common/version-checker" } diff --git a/clients/socks5/build.rs b/clients/socks5/build.rs deleted file mode 100644 index 56d753472c..0000000000 --- a/clients/socks5/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use built; - -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/clients/socks5/src/built_info.rs b/clients/socks5/src/built_info.rs deleted file mode 100644 index 38b46d59e6..0000000000 --- a/clients/socks5/src/built_info.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/clients/socks5/src/client/config/mod.rs b/clients/socks5/src/client/config/mod.rs index 6779ade230..705c7615f8 100644 --- a/clients/socks5/src/client/config/mod.rs +++ b/clients/socks5/src/client/config/mod.rs @@ -14,6 +14,7 @@ use crate::client::config::template::config_template; use client_core::config::Config as BaseConfig; +pub use client_core::config::MISSING_VALUE; use config::NymConfig; use nymsphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; @@ -37,10 +38,6 @@ impl NymConfig for Config { config_template() } - fn config_file_name() -> String { - "config.toml".to_string() - } - fn default_root_directory() -> PathBuf { dirs::home_dir() .expect("Failed to evaluate $HOME value") @@ -131,24 +128,3 @@ impl Default for Socks5 { } } } - -#[cfg(test)] -mod client_config { - use super::*; - - #[test] - fn after_saving_default_config_the_loaded_one_is_identical() { - // need to figure out how to do something similar but without touching the disk - // or the file system at all... - let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); - let fake_address = "CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML"; - let default_config = Config::new("foomp", fake_address); - default_config - .save_to_file(Some(temp_location.clone())) - .unwrap(); - - let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); - - assert_eq!(default_config, loaded_config); - } -} diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 80861a7bc4..939aa5da4e 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -24,11 +24,19 @@ pub(crate) fn config_template() -> &'static str { ##### main base client config options ##### [client] +# Version of the client for which this configuration was created. +version = '{{ client.version }}' + # Human readable ID of this particular client. id = '{{ client.id }}' -# URL to the directory server. -directory_server = '{{ client.directory_server }}' +# URL to the validator server for obtaining network topology. +validator_rest_url = '{{ client.validator_rest_url }}' + +# Special mode of the system such that all messages are sent as soon as they are received +# and no cover traffic is generated. If set all message delays are set to 0 and overwriting +# 'Debug' values will have no effect. +vpn_mode = {{ client.vpn_mode }} # Path to file containing private identity key. private_identity_key_file = '{{ client.private_identity_key_file }}' @@ -92,10 +100,10 @@ listening_port = {{ socks5.listening_port }} [debug] -average_packet_delay = {{ debug.average_packet_delay }} -average_ack_delay = {{ debug.average_ack_delay }} -loop_cover_traffic_average_delay = {{ debug.loop_cover_traffic_average_delay }} -message_sending_average_delay = {{ debug.message_sending_average_delay }} +average_packet_delay = '{{ debug.average_packet_delay }}' +average_ack_delay = '{{ debug.average_ack_delay }}' +loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}' +message_sending_average_delay = '{{ debug.message_sending_average_delay }}' "# } diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 727752a160..982f1feab7 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -23,7 +23,7 @@ use client_core::client::inbound_messages::{ }; use client_core::client::key_manager::KeyManager; use client_core::client::mix_traffic::{ - MixMessageReceiver, MixMessageSender, MixTrafficController, + BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController, }; use client_core::client::real_messages_control::RealMessagesController; use client_core::client::received_buffer::{ @@ -43,6 +43,7 @@ use gateway_client::{ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; +use nymsphinx::params::PacketMode; use tokio::runtime::Runtime; pub(crate) mod config; @@ -76,7 +77,7 @@ impl NymClient { pub fn as_mix_recipient(&self) -> Recipient { Recipient::new( *self.key_manager.identity_keypair().public_key(), - self.key_manager.encryption_keypair().public_key().clone(), + *self.key_manager.encryption_keypair().public_key(), // TODO: below only works under assumption that gateway address == gateway id // (which currently is true) NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(), @@ -88,7 +89,7 @@ impl NymClient { fn start_cover_traffic_stream( &self, topology_accessor: TopologyAccessor, - mix_tx: MixMessageSender, + mix_tx: BatchMixMessageSender, ) { info!("Starting loop cover traffic stream..."); // we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())" @@ -116,8 +117,14 @@ impl NymClient { reply_key_storage: ReplyKeyStorage, ack_receiver: AcknowledgementReceiver, input_receiver: InputMessageReceiver, - mix_sender: MixMessageSender, + mix_sender: BatchMixMessageSender, ) { + let packet_mode = if self.config.get_base().get_vpn_mode() { + PacketMode::VPN + } else { + PacketMode::Mix + }; + let controller_config = client_core::client::real_messages_control::Config::new( self.key_manager.ack_key(), self.config.get_base().get_ack_wait_multiplier(), @@ -126,6 +133,8 @@ impl NymClient { self.config.get_base().get_message_sending_average_delay(), self.config.get_base().get_average_packet_delay(), self.as_mix_recipient(), + packet_mode, + self.config.get_base().get_vpn_key_reuse_limit(), ); info!("Starting real traffic stream..."); @@ -142,7 +151,8 @@ impl NymClient { reply_key_storage, ) }); - real_messages_controller.start(self.runtime.handle()); + real_messages_controller + .start(self.runtime.handle(), self.config.get_base().get_vpn_mode()); } // buffer controlling all messages fetched from provider @@ -204,7 +214,7 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_base().get_directory_server(), + self.config.get_base().get_validator_rest_endpoint(), self.config.get_base().get_topology_refresh_rate(), ); let mut topology_refresher = @@ -213,7 +223,7 @@ impl NymClient { // components depending on topology would see a non-empty view info!( "Obtaining initial network topology from {}", - self.config.get_base().get_directory_server() + self.config.get_base().get_validator_rest_endpoint() ); self.runtime.block_on(topology_refresher.refresh()); @@ -238,7 +248,7 @@ impl NymClient { // requests? fn start_mix_traffic_controller( &mut self, - mix_rx: MixMessageReceiver, + mix_rx: BatchMixMessageReceiver, gateway_client: GatewayClient, ) { info!("Starting mix traffic controller..."); @@ -330,7 +340,9 @@ impl NymClient { input_receiver, sphinx_message_sender.clone(), ); - self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender); + if !self.config.get_base().get_vpn_mode() { + self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender); + } self.start_socks5_listener(received_buffer_request_sender, input_sender); info!("Client startup finished!"); diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index a22b5bfc85..54436ec8f5 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::built_info; use crate::client::config::Config; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; @@ -20,20 +19,13 @@ use client_core::client::key_manager::KeyManager; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use config::NymConfig; use crypto::asymmetric::identity; -use directory_client::DirectoryClient; use gateway_client::GatewayClient; use gateway_requests::registration::handshake::SharedKeys; use rand::{prelude::SliceRandom, rngs::OsRng}; -use std::convert::TryInto; use std::sync::Arc; use std::time::Duration; use topology::{gateway, NymTopology}; -const GOOD_GATEWAYS: [&str; 2] = [ - "D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr", - "5nrYxPR8gt2Gzo2BbHtsGf66KAEQY91WmM1eW78EphNy", -]; - pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("init") .about("Initialise a Nym client. Do this first!") @@ -54,9 +46,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Id of the gateway we are going to connect to.") .takes_value(true) ) - .arg(Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the client is getting topology from") + .arg(Arg::with_name("validator") + .long("validator") + .help("Address of the validator server the client is getting topology from") .takes_value(true), ) .arg(Arg::with_name("port") @@ -65,6 +57,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Port for the socket to listen on in all subsequent runs") .takes_value(true) ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("fastmode") .long("fastmode") .hidden(true) // this will prevent this flag from being displayed in `--help` @@ -93,28 +96,30 @@ async fn register_with_gateway( .expect("failed to register with the gateway!") } -async fn gateway_details(directory_server: &str, gateway_id: &str) -> gateway::Node { - let directory_client_config = directory_client::Config::new(directory_server.to_string()); - let directory_client = directory_client::Client::new(directory_client_config); - let topology = directory_client.get_topology().await.unwrap(); - let nym_topology: NymTopology = topology.try_into().expect("Invalid topology data!"); - let version_filtered_topology = nym_topology.filter_system_version(built_info::PKG_VERSION); +async fn gateway_details(validator_server: &str, chosen_gateway_id: Option<&str>) -> gateway::Node { + let validator_client_config = validator_client::Config::new(validator_server.to_string()); + let validator_client = validator_client::Client::new(validator_client_config); + let topology = validator_client.get_active_topology().await.unwrap(); + let nym_topology: NymTopology = topology.into(); + let version_filtered_topology = nym_topology.filter_system_version(env!("CARGO_PKG_VERSION")); - version_filtered_topology - .gateways() - .iter() - .find(|gateway| gateway.identity_key.to_base58_string() == gateway_id) - .expect(&*format!("no gateway with id {} exists!", gateway_id)) - .clone() -} + // if we have chosen particular gateway - use it, otherwise choose a random one. + // (remember that in active topology all gateways have at least 100 reputation so should + // be working correctly) -fn select_gateway(arg: Option<&str>) -> &str { - if let Some(gateway_id) = arg { - gateway_id + if let Some(gateway_id) = chosen_gateway_id { + version_filtered_topology + .gateways() + .iter() + .find(|gateway| gateway.identity_key.to_base58_string() == gateway_id) + .expect(&*format!("no gateway with id {} exists!", gateway_id)) + .clone() } else { - // TODO1: this should only be done on testnet - // TODO2: it should probably check if chosen gateway is actually online - GOOD_GATEWAYS.choose(&mut rand::thread_rng()).unwrap() + version_filtered_topology + .gateways() + .choose(&mut rand::thread_rng()) + .expect("there are no gateways on the network!") + .clone() } } @@ -124,7 +129,15 @@ pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); // required for now let provider_address = matches.value_of("provider").unwrap(); + let already_init = if Config::default_config_file_path(id).exists() { + println!("Socks5 client \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id); + true + } else { + false + }; + let mut config = Config::new(id, provider_address); + let mut rng = OsRng; // TODO: ideally that should be the last thing that's being done to config. @@ -134,33 +147,42 @@ pub fn execute(matches: &ArgMatches) { config.get_base_mut().set_high_default_traffic_volume(); } - // create identity, encryption and ack keys. - let mut key_manager = KeyManager::new(&mut rng); + // if client was already initialised, don't generate new keys, not re-register with gateway + // (because this would create new shared key) + if !already_init { + // create identity, encryption and ack keys. + let mut key_manager = KeyManager::new(&mut rng); - let gateway_id = select_gateway(matches.value_of("gateway")); - config.get_base_mut().with_gateway_id(gateway_id); + let chosen_gateway_id = matches.value_of("gateway"); - let registration_fut = async { - let gate_details = - gateway_details(&config.get_base().get_directory_server(), gateway_id).await; - let shared_keys = - register_with_gateway(&gate_details, key_manager.identity_keypair()).await; - (shared_keys, gate_details.client_listener) - }; + let registration_fut = async { + let gate_details = gateway_details( + &config.get_base().get_validator_rest_endpoint(), + chosen_gateway_id, + ) + .await; + config + .get_base_mut() + .with_gateway_id(gate_details.identity_key.to_base58_string()); + let shared_keys = + register_with_gateway(&gate_details, key_manager.identity_keypair()).await; + (shared_keys, gate_details.client_listener) + }; - // TODO: is there perhaps a way to make it work without having to spawn entire runtime? - let mut rt = tokio::runtime::Runtime::new().unwrap(); - let (shared_keys, gateway_listener) = rt.block_on(registration_fut); - config - .get_base_mut() - .with_gateway_listener(gateway_listener); - key_manager.insert_gateway_shared_key(shared_keys); + // TODO: is there perhaps a way to make it work without having to spawn entire runtime? + let mut rt = tokio::runtime::Runtime::new().unwrap(); + let (shared_keys, gateway_listener) = rt.block_on(registration_fut); + config + .get_base_mut() + .with_gateway_listener(gateway_listener); + key_manager.insert_gateway_shared_key(shared_keys); - let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); - key_manager - .store_keys(&pathfinder) - .expect("Failed to generated keys"); - println!("Saved all generated keys"); + let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); + key_manager + .store_keys(&pathfinder) + .expect("Failed to generated keys"); + println!("Saved all generated keys"); + } let config_save_location = config.get_config_file_save_location(); config diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 2906c40d9e..ef75167f5b 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -15,18 +15,23 @@ use crate::client::config::Config; use clap::ArgMatches; -pub mod init; -pub mod run; +pub(crate) mod init; +pub(crate) mod run; +pub(crate) mod upgrade; pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { - if let Some(directory) = matches.value_of("directory") { - config.get_base_mut().with_custom_directory(directory); + if let Some(validator) = matches.value_of("validator") { + config.get_base_mut().set_custom_validator(validator); } if let Some(gateway_id) = matches.value_of("gateway") { config.get_base_mut().with_gateway_id(gateway_id); } + if matches.is_present("vpn-mode") { + config.get_base_mut().set_vpn_mode(true); + } + if let Some(port) = matches.value_of("port").map(|port| port.parse::()) { if let Err(err) = port { // if port was overridden, it must be parsable diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index f8c9aea11c..e1f8e840c9 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -17,6 +17,8 @@ use crate::client::NymClient; use crate::commands::override_config; use clap::{App, Arg, ArgMatches}; use config::NymConfig; +use log::*; +use version_checker::is_minor_version_compatible; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("run") @@ -38,9 +40,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Address of the socks5 provider to send messages to.") .takes_value(true) ) - .arg(Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the client is getting topology from") + .arg(Arg::with_name("validator") + .long("validator") + .help("Address of the validator server the client is getting topology from") .takes_value(true), ) .arg(Arg::with_name("gateway") @@ -48,6 +50,17 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened") .takes_value(true) ) + .arg(Arg::with_name("vpn-mode") + .long("vpn-mode") + .help("Set the vpn mode of the client") + .long_help( + r#" + Special mode of the system such that all messages are sent as soon as they are received + and no cover traffic is generated. If set all message delays are set to 0 and overwriting + 'Debug' values will have no effect. + "# + ) + ) .arg(Arg::with_name("port") .short("p") .long("port") @@ -56,14 +69,42 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { ) } +// this only checks compatibility between config the binary. It does not take into consideration +// network version. It might do so in the future. +fn version_check(cfg: &Config) -> bool { + let binary_version = env!("CARGO_PKG_VERSION"); + let config_version = cfg.get_base().get_version(); + if binary_version != config_version { + warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version); + if is_minor_version_compatible(binary_version, config_version) { + info!("but they are still semver compatible. However, consider running the `upgrade` command"); + true + } else { + error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); + false + } + } else { + true + } +} + pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); - let mut config = - Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id)) - .expect("Failed to load config file"); + let mut config = match Config::load_from_file(id) { + Ok(cfg) => cfg, + Err(err) => { + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err); + return; + } + }; config = override_config(config, matches); + if !version_check(&config) { + error!("failed the local version check"); + return; + } + NymClient::new(config).run_forever(); } diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs new file mode 100644 index 0000000000..6d44227286 --- /dev/null +++ b/clients/socks5/src/commands/upgrade.rs @@ -0,0 +1,187 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client::config::{Config, MISSING_VALUE}; +use clap::{App, Arg, ArgMatches}; +use client_core::config::DEFAULT_VALIDATOR_REST_ENDPOINT; +use config::NymConfig; +use std::fmt::Display; +use std::process; +use version_checker::{parse_version, Version}; + +fn print_start_upgrade(from: D1, to: D2) { + println!( + "\n==================\nTrying to upgrade mixnode from {} to {} ...", + from, to + ); +} + +fn print_failed_upgrade(from: D1, to: D2) { + eprintln!( + "Upgrade from {} to {} failed!\n==================\n", + from, to + ); +} + +fn print_successful_upgrade(from: D1, to: D2) { + println!( + "Upgrade from {} to {} was successful!\n==================\n", + from, to + ); +} + +fn pre_090_upgrade(from: &str, mut config: Config) -> Config { + // this is not extracted to separate function as you only have to manually pass version + // if upgrading from pre090 version + let from = match from.strip_prefix("v") { + Some(stripped) => stripped, + None => from, + }; + + let from = match from.strip_prefix("V") { + Some(stripped) => stripped, + None => from, + }; + + let from_version = parse_version(from).expect("invalid version provided!"); + if from_version.major == 0 && from_version.minor < 8 { + // technically this could be implemented, but is there any point in that? + eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead"); + process::exit(1) + } + + if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { + eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); + process::exit(1) + } + + // note: current is guaranteed to not have any `build` information suffix (nor pre-release + // information), as this was asserted at the beginning of this command) + // + // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate + // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) + // this way we don't need to have all the crazy paths on how to upgrade from any version to any + // other version. We just upgrade one minor version at a time. + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + let to_version = if current.major == 0 && current.minor == 9 { + current + } else { + Version::new(0, 9, 0) + }; + + if config.get_base().get_validator_rest_endpoint() != MISSING_VALUE { + eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + } + + print_start_upgrade(&from_version, &to_version); + + config + .get_base_mut() + .set_custom_version(to_version.to_string().as_ref()); + + println!( + "Setting validator REST endpoint to to {}", + DEFAULT_VALIDATOR_REST_ENDPOINT + ); + + config + .get_base_mut() + .set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + + config.save_to_file(None).unwrap_or_else(|err| { + eprintln!("failed to overwrite config file! - {:?}", err); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + }); + + print_successful_upgrade(from_version, to_version); + + config +} + +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the mixnode") + .arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-socks5-client we want to upgrade") + .takes_value(true) + .required(true), + ) + // the rest of arguments depend on the upgrade path + .arg(Arg::with_name("current version") + .long("current-version") + .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") + .takes_value(true) + ) +} + +pub fn execute(matches: &ArgMatches) { + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + + // technically this is not a correct way of checking it as a released version might contain valid build identifiers + // however, we are not using them ourselves at the moment and hence it should be fine. + // if we change our mind, we could easily tweak this code + if current.is_prerelease() || !current.build.is_empty() { + eprintln!( + "Trying to upgrade to a non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + let id = matches.value_of("id").unwrap(); + + let mut existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {:?}", err); + process::exit(1) + }); + + // versions fields were added in 0.9.0 + if existing_config.get_base().get_version() == MISSING_VALUE { + let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { + eprintln!( + "trying to upgrade from pre v0.9.0 without providing current system version!" + ); + process::exit(1) + }); + + // upgrades up to 0.9.0 + existing_config = pre_090_upgrade(self_reported_version, existing_config); + } + + let config_version = + Version::parse(existing_config.get_base().get_version()).unwrap_or_else(|err| { + eprintln!("failed to parse node version! - {:?}", err); + process::exit(1) + }); + + if config_version.is_prerelease() || !config_version.build.is_empty() { + eprintln!( + "Trying to upgrade to from non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + // here be upgrade path to 0.10.0 and beyond based on version number from config + if config_version == current { + println!("You're using the most recent version!"); + } else { + eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current); + process::exit(1) + } +} diff --git a/clients/socks5/src/lib.rs b/clients/socks5/src/lib.rs index 694607032a..c200a3501e 100644 --- a/clients/socks5/src/lib.rs +++ b/clients/socks5/src/lib.rs @@ -11,6 +11,5 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -pub mod built_info; pub mod client; pub mod socks; diff --git a/clients/socks5/src/main.rs b/clients/socks5/src/main.rs index dfcbc72f15..a04f8ec90d 100644 --- a/clients/socks5/src/main.rs +++ b/clients/socks5/src/main.rs @@ -14,7 +14,6 @@ use clap::{App, ArgMatches}; -pub mod built_info; pub mod client; mod commands; pub mod socks; @@ -25,11 +24,12 @@ fn main() { println!("{}", banner()); let arg_matches = App::new("Nym Socks5 Proxy") - .version(built_info::PKG_VERSION) + .version(env!("CARGO_PKG_VERSION")) .author("Nymtech") .about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address") .subcommand(commands::init::command_args()) .subcommand(commands::run::command_args()) + .subcommand(commands::upgrade::command_args()) .get_matches(); execute(arg_matches); @@ -39,6 +39,7 @@ fn execute(matches: ArgMatches) { match matches.subcommand() { ("init", Some(m)) => commands::init::execute(m), ("run", Some(m)) => commands::run::execute(m), + ("upgrade", Some(m)) => commands::upgrade::execute(m), _ => println!("{}", usage()), } } @@ -60,7 +61,7 @@ fn banner() -> String { (socks5 proxy - version {:}) "#, - built_info::PKG_VERSION + env!("CARGO_PKG_VERSION") ) } diff --git a/clients/socks5/src/socks/client.rs b/clients/socks5/src/socks/client.rs index 9e782d30ac..db17fe5640 100644 --- a/clients/socks5/src/socks/client.rs +++ b/clients/socks5/src/socks/client.rs @@ -10,15 +10,13 @@ use futures::channel::mpsc; use futures::task::{Context, Poll}; use log::*; use nymsphinx::addressing::clients::Recipient; -use ordered_buffer::{OrderedMessageBuffer, OrderedMessageSender}; use pin_project::pin_project; -use proxy_helpers::available_reader::AvailableReader; use proxy_helpers::connection_controller::{ ConnectionReceiver, ControllerCommand, ControllerSender, }; use proxy_helpers::proxy_runner::ProxyRunner; use rand::RngCore; -use socks5_requests::{ConnectionId, Request}; +use socks5_requests::{ConnectionId, RemoteAddress, Request}; use std::net::{Shutdown, SocketAddr}; use std::pin::Pin; use tokio::prelude::*; @@ -145,8 +143,7 @@ pub(crate) struct SocksClient { impl Drop for SocksClient { fn drop(&mut self) { // TODO: decrease to debug/trace - info!("socksclient is going out of scope - the stream is getting dropped!"); - info!("Connection {} is getting closed", self.connection_id); + debug!("Connection {} is getting closed", self.connection_id); self.controller_sender .unbounded_send(ControllerCommand::Remove(self.connection_id)) .unwrap(); @@ -222,26 +219,38 @@ impl SocksClient { Ok(()) } - async fn send_request_to_mixnet(&mut self, request: Request) { - self.send_to_mixnet(request.into_bytes()).await; + async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) { + let req = Request::new_connect( + self.connection_id, + remote_address.clone(), + self.self_address.clone(), + ); + + let input_message = + InputMessage::new_fresh(self.service_provider.clone(), req.into_bytes(), false); + self.input_sender.unbounded_send(input_message).unwrap(); } - async fn run_proxy( - &mut self, - conn_receiver: ConnectionReceiver, - message_sender: OrderedMessageSender, - ) { + async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) { + self.send_connect_to_mixnet(remote_proxy_target.clone()) + .await; + let stream = self.stream.run_proxy(); + let local_stream_remote = stream + .peer_addr() + .expect("failed to extract peer address") + .to_string(); let connection_id = self.connection_id; let input_sender = self.input_sender.clone(); - let recipient = self.service_provider.clone(); + let recipient = self.service_provider; let (stream, _) = ProxyRunner::new( stream, + local_stream_remote, + remote_proxy_target, conn_receiver, input_sender, connection_id, - message_sender, ) .run(move |conn_id, read_data, socket_closed| { let provider_request = Request::new_send(conn_id, read_data, socket_closed); @@ -262,14 +271,9 @@ impl SocksClient { // setup for receiving from the mixnet let (mix_sender, mix_receiver) = mpsc::unbounded(); - let ordered_buffer = OrderedMessageBuffer::new(); self.controller_sender - .unbounded_send(ControllerCommand::Insert( - self.connection_id, - mix_sender, - ordered_buffer, - )) + .unbounded_send(ControllerCommand::Insert(self.connection_id, mix_sender)) .unwrap(); match request.command { @@ -278,26 +282,16 @@ impl SocksClient { trace!("Connecting to: {:?}", remote_address.clone()); self.acknowledge_socks5().await; - let mut message_sender = OrderedMessageSender::new(); - // 'connect' needs to be handled manually due to different structure, - // but still needs to have correct sequence number on it! - - // read whatever we can - let available_reader = AvailableReader::new(&mut self.stream); - let (request_data_bytes, _) = available_reader.await?; - let ordered_message = message_sender.wrap_message(request_data_bytes.to_vec()); - - let socks_provider_request = Request::new_connect( - self.connection_id, + info!( + "Starting proxy for {} (id: {})", remote_address.clone(), - ordered_message, - self.self_address.clone(), + self.connection_id + ); + self.run_proxy(mix_receiver, remote_address.clone()).await; + info!( + "Proxy for {} is finished (id: {})", + remote_address, self.connection_id ); - - self.send_request_to_mixnet(socks_provider_request).await; - info!("Starting proxy for {}", remote_address.clone()); - self.run_proxy(mix_receiver, message_sender).await; - info!("Proxy for {} is finished", remote_address); } SocksCommand::Bind => unimplemented!(), // not handled @@ -307,16 +301,6 @@ impl SocksClient { Ok(()) } - /// Send serialized Socks5 request bytes to the mixnet. The request stream - /// will be chunked up into a series of one or more Sphinx packets and - /// reassembled at the destination service provider at the other end, then - /// sent onwards anonymously. - async fn send_to_mixnet(&self, request_bytes: Vec) { - let input_message = - InputMessage::new_fresh(self.service_provider.clone(), request_bytes, false); - self.input_sender.unbounded_send(input_message).unwrap(); - } - /// Writes a Socks5 header back to the requesting client's TCP stream, /// basically saying "I acknowledge your request and am dealing with it". async fn acknowledge_socks5(&mut self) { @@ -381,27 +365,12 @@ impl SocksClient { // Username parsing let ulen = header[1]; - - let mut username = Vec::with_capacity(ulen as usize); - - // For some reason the vector needs to actually be full - for _ in 0..ulen { - username.push(0); - } - + let mut username = vec![0; ulen as usize]; self.stream.read_exact(&mut username).await?; // Password Parsing - let mut plen = [0u8; 1]; - self.stream.read_exact(&mut plen).await?; - - let mut password = Vec::with_capacity(plen[0] as usize); - - // For some reason the vector needs to actually be full - for _ in 0..plen[0] { - password.push(0); - } - + let plen = self.stream.read_u8().await?; + let mut password = vec![0; plen as usize]; self.stream.read_exact(&mut password).await?; let username_str = String::from_utf8(username)?; diff --git a/clients/socks5/src/socks/request.rs b/clients/socks5/src/socks/request.rs index d3e4fac611..5869d0d60d 100644 --- a/clients/socks5/src/socks/request.rs +++ b/clients/socks5/src/socks/request.rs @@ -1,6 +1,7 @@ use super::types::{AddrType, ResponseCode, SocksProxyError}; use super::{utils as socks_utils, SOCKS_VERSION}; use log::*; +use std::fmt::{self, Display}; use tokio::prelude::*; /// A Socks5 request hitting the proxy. @@ -95,12 +96,14 @@ impl SocksRequest { port, }) } +} +impl Display for SocksRequest { /// Print out the address and port to a String. /// This might return domain:port, ipv6:port, or ipv4:port. - pub(crate) fn to_string(&self) -> String { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr); - format!("{}:{}", address, self.port) + write!(f, "{}:{}", address, self.port) } } diff --git a/clients/socks5/src/socks/server.rs b/clients/socks5/src/socks/server.rs index 6350bed3fe..e222b106a7 100644 --- a/clients/socks5/src/socks/server.rs +++ b/clients/socks5/src/socks/server.rs @@ -72,9 +72,9 @@ impl SphinxSocksServer { stream, self.authenticator.clone(), input_sender.clone(), - self.service_provider.clone(), + self.service_provider, controller_sender.clone(), - self.self_address.clone(), + self.self_address, ); tokio::spawn(async move { diff --git a/clients/webassembly/Cargo.toml b/clients/webassembly/Cargo.toml index db3004b0ac..74bc66287e 100644 --- a/clients/webassembly/Cargo.toml +++ b/clients/webassembly/Cargo.toml @@ -1,8 +1,7 @@ [package] -build = "build.rs" name = "nym-client-wasm" authors = ["Dave Hrycyszyn ", "Jedrzej Stuczynski "] -version = "0.8.1" +version = "0.9.0" edition = "2018" keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"] license = "Apache-2.0" @@ -28,8 +27,8 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] } crypto = { path = "../../common/crypto" } nymsphinx = { path = "../../common/nymsphinx" } topology = { path = "../../common/topology" } -directory-client = { path = "../../common/client-libs/directory-client" } gateway-client = { path = "../../common/client-libs/gateway-client" } +validator-client = { path = "../../common/client-libs/validator-client" } wasm-utils = { path = "../../common/wasm-utils" } # The `console_error_panic_hook` crate provides better debugging of panics by @@ -45,14 +44,10 @@ console_error_panic_hook = { version = "0.1", optional = true } # Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. wee_alloc = { version = "0.4", optional = true } -[build-dependencies] -built = "0.4.3" - [dev-dependencies] wasm-bindgen-test = "0.2" [package.metadata.wasm-pack.profile.release] -# this needs to be disabled until https://github.com/rustwasm/wasm-pack/issues/886 is resolved -wasm-opt = ["-Oz", "--enable-mutable-globals"] +wasm-opt = false diff --git a/clients/webassembly/build.rs b/clients/webassembly/build.rs deleted file mode 100644 index 56d753472c..0000000000 --- a/clients/webassembly/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use built; - -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/clients/webassembly/js-example/index.js b/clients/webassembly/js-example/index.js index 54a46254d1..05842638ba 100644 --- a/clients/webassembly/js-example/index.js +++ b/clients/webassembly/js-example/index.js @@ -24,10 +24,10 @@ async function main() { // sets up better stack traces in case of in-rust panics set_panic_hook(); - // directory server we will use to get topology from - const directory = "https://qa-directory.nymtech.net"; //"http://localhost:8080"; + // validator server we will use to get topology from + const validator = "http://testnet-validator1.nymtech.net:8081"; //"http://localhost:8081"; - client = new NymClient(directory); + client = new NymClient(validator); const on_message = (msg) => displayReceived(msg); const on_connect = () => console.log("Established (and authenticated) gateway connection!"); diff --git a/clients/webassembly/js-example/package-lock.json b/clients/webassembly/js-example/package-lock.json index 61890a61d3..26381bfa71 100644 --- a/clients/webassembly/js-example/package-lock.json +++ b/clients/webassembly/js-example/package-lock.json @@ -358,9 +358,10 @@ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { - "bn.js": "4.11.8", + "bn.js": "4.11.9", "inherits": "2.0.4", - "minimalistic-assert": "1.0.1" + "minimalistic-assert": "1.0.1", + "safer-buffer": "2.1.2" }, "dependencies": { "bn.js": { @@ -410,7 +411,7 @@ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "lodash": "4.17.15" + "lodash": "4.17.20" } }, "async-each": { @@ -662,44 +663,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "requires": { - "bn.js": "5.1.1", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "elliptic": "6.5.2", - "inherits": "2.0.4", - "parse-asn1": "5.1.5", - "readable-stream": "3.6.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "2.0.4", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true } } @@ -823,9 +789,9 @@ } }, "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", "dev": true, "optional": true, "requires": { @@ -836,7 +802,7 @@ "is-binary-path": "2.1.0", "is-glob": "4.0.1", "normalize-path": "3.0.0", - "readdirp": "3.4.0" + "readdirp": "3.5.0" }, "dependencies": { "braces": { @@ -1113,7 +1079,7 @@ "normalize-path": "3.0.0", "p-limit": "2.3.0", "schema-utils": "1.0.0", - "serialize-javascript": "2.1.2", + "serialize-javascript": "4.0.0", "webpack-log": "2.0.0" } }, @@ -1123,24 +1089,6 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "elliptic": "6.5.2" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } - } - }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -1198,6 +1146,66 @@ "public-encrypt": "4.0.3", "randombytes": "2.1.0", "randomfill": "1.0.4" + }, + "dependencies": { + "browserify-sign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.1.0.tgz", + "integrity": "sha512-VYxo7cDCeYUoBZ0ZCy4UyEUCP3smyBd4DRQM5nrFS1jJjPJjX7rP3oLRpPoWfkhQfyJ0I9ZbHbKafrFD/SGlrg==", + "dev": true, + "requires": { + "bn.js": "5.1.3", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "elliptic": "6.5.3", + "inherits": "2.0.4", + "parse-asn1": "5.1.6", + "readable-stream": "3.6.0" + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "4.11.9", + "elliptic": "6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "2.0.4", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + } } }, "cyclist": { @@ -1385,9 +1393,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true } } @@ -1466,9 +1474,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true } } @@ -1582,15 +1590,17 @@ "requires": { "esrecurse": "4.2.1", "estraverse": "4.3.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "4.3.0" + }, + "dependencies": { + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.3.0" + } + } } }, "estraverse": { @@ -1611,12 +1621,6 @@ "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", "dev": true }, - "events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", - "dev": true - }, "eventsource": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", @@ -2336,7 +2340,7 @@ "requires": { "http-proxy": "1.18.1", "is-glob": "4.0.1", - "lodash": "4.17.15", + "lodash": "4.17.20", "micromatch": "3.1.10" } }, @@ -2490,7 +2494,7 @@ "dev": true, "optional": true, "requires": { - "binary-extensions": "2.0.0" + "binary-extensions": "2.1.0" } }, "is-buffer": { @@ -2723,15 +2727,6 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "2.0.0" - } - }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -2760,9 +2755,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "loglevel": { @@ -2790,15 +2785,6 @@ "semver": "5.7.1" } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "1.0.0" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -2831,17 +2817,6 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "0.1.3", - "mimic-fn": "2.1.0", - "p-is-promise": "2.1.0" - } - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -2896,9 +2871,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true } } @@ -3073,9 +3048,9 @@ "dev": true }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, "node-libs-browser": { @@ -3109,6 +3084,12 @@ "vm-browserify": "1.1.2" }, "dependencies": { + "events": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "dev": true + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -3275,23 +3256,6 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "1.0.0", - "lcid": "2.0.0", - "mem": "4.3.0" - } - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -3360,12 +3324,26 @@ "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "dev": true, "requires": { - "asn1.js": "4.10.1", + "asn1.js": "5.4.1", "browserify-aes": "1.2.0", - "create-hash": "1.2.0", "evp_bytestokey": "1.0.3", "pbkdf2": "3.0.17", "safe-buffer": "5.1.2" + }, + "dependencies": { + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" + } + } } }, "parse-passwd": { @@ -3576,11 +3554,36 @@ "safe-buffer": "5.1.2" }, "dependencies": { + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "inherits": "2.0.4", + "minimalistic-assert": "1.0.1" + } + }, "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "requires": { + "asn1.js": "4.10.1", + "browserify-aes": "1.2.0", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.1.1", + "safe-buffer": "5.1.2" + } } } }, @@ -3708,9 +3711,9 @@ } }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dev": true, "optional": true, "requires": { @@ -3896,12 +3899,12 @@ "dev": true }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "dev": true, "requires": { - "node-forge": "0.9.0" + "node-forge": "0.10.0" } }, "semver": { @@ -3945,7 +3948,7 @@ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "randombytes": "2.1.0" } }, "serve-index": { @@ -4563,9 +4566,9 @@ "find-cache-dir": "2.1.0", "is-wsl": "1.1.0", "schema-utils": "1.0.0", - "serialize-javascript": "2.1.2", + "serialize-javascript": "4.0.0", "source-map": "0.6.1", - "terser": "4.6.13", + "terser": "4.8.0", "webpack-sources": "1.4.3", "worker-farm": "1.7.0" }, @@ -4875,9 +4878,9 @@ "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", "dev": true, "requires": { - "chokidar": "3.4.0", + "chokidar": "3.4.3", "graceful-fs": "4.2.4", - "neo-async": "2.6.1", + "neo-async": "2.6.2", "watchpack-chokidar2": "2.0.0" } }, @@ -4996,7 +4999,7 @@ "ajv": "6.12.2", "ajv-keywords": "3.4.1", "chrome-trace-event": "1.0.2", - "enhanced-resolve": "4.1.1", + "enhanced-resolve": "4.3.0", "eslint-scope": "4.0.3", "json-parse-better-errors": "1.0.2", "loader-runner": "2.4.0", @@ -5008,58 +5011,59 @@ "node-libs-browser": "2.2.1", "schema-utils": "1.0.0", "tapable": "1.1.3", - "terser-webpack-plugin": "1.4.3", - "watchpack": "1.7.2", + "terser-webpack-plugin": "1.4.5", + "watchpack": "1.7.4", "webpack-sources": "1.4.3" + }, + "dependencies": { + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "dev": true, + "requires": { + "graceful-fs": "4.2.4", + "memory-fs": "0.5.0", + "tapable": "1.1.3" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "0.1.7", + "readable-stream": "2.3.7" + } + } + } + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + } } }, "webpack-cli": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz", - "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", "dev": true, "requires": { "chalk": "2.4.2", "cross-spawn": "6.0.5", - "enhanced-resolve": "4.1.0", + "enhanced-resolve": "4.3.0", "findup-sync": "3.0.0", "global-modules": "2.0.0", "import-local": "2.0.0", - "interpret": "1.2.0", - "loader-utils": "1.2.3", + "interpret": "1.4.0", + "loader-utils": "1.4.0", "supports-color": "6.1.0", - "v8-compile-cache": "2.0.3", - "yargs": "13.2.4" - }, - "dependencies": { - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", - "dev": true, - "requires": { - "graceful-fs": "4.2.4", - "memory-fs": "0.4.1", - "tapable": "1.1.3" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "5.2.2", - "emojis-list": "2.1.0", - "json5": "1.0.1" - } - } + "v8-compile-cache": "2.1.1", + "yargs": "13.3.2" } }, "webpack-dev-middleware": { @@ -5109,7 +5113,7 @@ "p-retry": "3.0.1", "portfinder": "1.0.26", "schema-utils": "1.0.0", - "selfsigned": "1.10.7", + "selfsigned": "1.10.8", "semver": "6.3.0", "serve-index": "1.9.1", "sockjs": "0.3.20", @@ -5374,7 +5378,6 @@ "cliui": "5.0.0", "find-up": "3.0.0", "get-caller-file": "2.0.5", - "os-locale": "3.1.0", "require-directory": "2.1.1", "require-main-filename": "2.0.0", "set-blocking": "2.0.0", diff --git a/clients/webassembly/react-example/package-lock.json b/clients/webassembly/react-example/package-lock.json index 1e64ce46c6..3cb3219c4f 100644 --- a/clients/webassembly/react-example/package-lock.json +++ b/clients/webassembly/react-example/package-lock.json @@ -13,50 +13,33 @@ } }, "@babel/compat-data": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", - "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", - "requires": { - "browserslist": "4.14.1", - "invariant": "2.2.4", - "semver": "5.7.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.1.tgz", + "integrity": "sha512-725AQupWJZ8ba0jbKceeFblZTY90McUBWMwHhkFQ9q1zKPJ95GUktljFcgcsIVwRnTnRKlcYzfiNImg5G9m6ZQ==" }, "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", "requires": { "@babel/code-frame": "7.10.4", - "@babel/generator": "7.11.6", - "@babel/helper-module-transforms": "7.11.0", - "@babel/helpers": "7.10.4", - "@babel/parser": "7.11.5", + "@babel/generator": "7.12.1", + "@babel/helper-module-transforms": "7.12.1", + "@babel/helpers": "7.12.1", + "@babel/parser": "7.12.3", "@babel/template": "7.10.4", - "@babel/traverse": "7.11.5", - "@babel/types": "7.11.5", + "@babel/traverse": "7.12.1", + "@babel/types": "7.12.1", "convert-source-map": "1.7.0", - "debug": "4.1.1", + "debug": "4.2.0", "gensync": "1.0.0-beta.1", "json5": "2.1.3", "lodash": "4.17.20", - "resolve": "1.17.0", + "resolve": "1.18.1", "semver": "5.7.1", "source-map": "0.5.7" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -65,11 +48,11 @@ } }, "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", + "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", "requires": { - "@babel/types": "7.11.5", + "@babel/types": "7.12.1", "jsesc": "2.5.2", "source-map": "0.5.7" }, @@ -86,7 +69,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { @@ -94,8 +77,8 @@ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "requires": { - "@babel/helper-explode-assignable-expression": "7.11.4", - "@babel/types": "7.11.5" + "@babel/helper-explode-assignable-expression": "7.12.1", + "@babel/types": "7.12.1" } }, "@babel/helper-builder-react-jsx": { @@ -104,59 +87,50 @@ "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", "requires": { "@babel/helper-annotate-as-pure": "7.10.4", - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-builder-react-jsx-experimental": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.11.5.tgz", - "integrity": "sha512-Vc4aPJnRZKWfzeCBsqTBnzulVNjABVdahSPhtdMD3Vs80ykx4a87jTHtF/VR+alSrDmNvat7l13yrRHauGcHVw==", + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.4.tgz", + "integrity": "sha512-AjEa0jrQqNk7eDQOo0pTfUOwQBMF+xVqrausQwT9/rTKy0g04ggFNaJpaE09IQMn9yExluigWMJcj0WC7bq+Og==", "requires": { "@babel/helper-annotate-as-pure": "7.10.4", - "@babel/helper-module-imports": "7.10.4", - "@babel/types": "7.11.5" + "@babel/helper-module-imports": "7.12.1", + "@babel/types": "7.12.1" } }, "@babel/helper-compilation-targets": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", - "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.1.tgz", + "integrity": "sha512-jtBEif7jsPwP27GPHs06v4WBV0KrE8a/P7n0N0sSvHn2hwUCYnolP/CLmz51IzAW4NlN+HuoBtb9QcwnRo9F/g==", "requires": { - "@babel/compat-data": "7.11.0", - "browserslist": "4.14.1", - "invariant": "2.2.4", - "levenary": "1.1.1", + "@babel/compat-data": "7.12.1", + "@babel/helper-validator-option": "7.12.1", + "browserslist": "4.14.5", "semver": "5.7.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", "requires": { "@babel/helper-function-name": "7.10.4", - "@babel/helper-member-expression-to-functions": "7.11.0", + "@babel/helper-member-expression-to-functions": "7.12.1", "@babel/helper-optimise-call-expression": "7.10.4", - "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-replace-supers": "7.10.4", + "@babel/helper-replace-supers": "7.12.1", "@babel/helper-split-export-declaration": "7.11.0" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", - "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz", + "integrity": "sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA==", "requires": { "@babel/helper-annotate-as-pure": "7.10.4", "@babel/helper-regex": "7.10.5", - "regexpu-core": "4.7.0" + "regexpu-core": "4.7.1" } }, "@babel/helper-define-map": { @@ -165,16 +139,16 @@ "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "requires": { "@babel/helper-function-name": "7.10.4", - "@babel/types": "7.11.5", + "@babel/types": "7.12.1", "lodash": "4.17.20" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", - "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-function-name": { @@ -184,7 +158,7 @@ "requires": { "@babel/helper-get-function-arity": "7.10.4", "@babel/template": "7.10.4", - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-get-function-arity": { @@ -192,7 +166,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-hoist-variables": { @@ -200,36 +174,38 @@ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz", + "integrity": "sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz", + "integrity": "sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", "requires": { - "@babel/helper-module-imports": "7.10.4", - "@babel/helper-replace-supers": "7.10.4", - "@babel/helper-simple-access": "7.10.4", + "@babel/helper-module-imports": "7.12.1", + "@babel/helper-replace-supers": "7.12.1", + "@babel/helper-simple-access": "7.12.1", "@babel/helper-split-export-declaration": "7.11.0", + "@babel/helper-validator-identifier": "7.10.4", "@babel/template": "7.10.4", - "@babel/types": "7.11.5", + "@babel/traverse": "7.12.1", + "@babel/types": "7.12.1", "lodash": "4.17.20" } }, @@ -238,7 +214,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-plugin-utils": { @@ -255,42 +231,40 @@ } }, "@babel/helper-remap-async-to-generator": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", - "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", "requires": { "@babel/helper-annotate-as-pure": "7.10.4", - "@babel/helper-wrap-function": "7.10.4", - "@babel/template": "7.10.4", - "@babel/types": "7.11.5" + "@babel/helper-wrap-function": "7.12.3", + "@babel/types": "7.12.1" } }, "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz", + "integrity": "sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==", "requires": { - "@babel/helper-member-expression-to-functions": "7.11.0", + "@babel/helper-member-expression-to-functions": "7.12.1", "@babel/helper-optimise-call-expression": "7.10.4", - "@babel/traverse": "7.11.5", - "@babel/types": "7.11.5" + "@babel/traverse": "7.12.1", + "@babel/types": "7.12.1" } }, "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", "requires": { - "@babel/template": "7.10.4", - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", - "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-split-export-declaration": { @@ -298,7 +272,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@babel/helper-validator-identifier": { @@ -306,25 +280,30 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, + "@babel/helper-validator-option": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", + "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==" + }, "@babel/helper-wrap-function": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", - "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", "requires": { "@babel/helper-function-name": "7.10.4", "@babel/template": "7.10.4", - "@babel/traverse": "7.11.5", - "@babel/types": "7.11.5" + "@babel/traverse": "7.12.1", + "@babel/types": "7.12.1" } }, "@babel/helpers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", - "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.1.tgz", + "integrity": "sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==", "requires": { "@babel/template": "7.10.4", - "@babel/traverse": "7.11.5", - "@babel/types": "7.11.5" + "@babel/traverse": "7.12.1", + "@babel/types": "7.12.1" } }, "@babel/highlight": { @@ -338,137 +317,137 @@ } }, "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==" + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", + "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", - "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", + "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-remap-async-to-generator": "7.11.4", + "@babel/helper-remap-async-to-generator": "7.12.1", "@babel/plugin-syntax-async-generators": "7.8.4" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", - "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", "requires": { - "@babel/helper-create-class-features-plugin": "7.10.5", + "@babel/helper-create-class-features-plugin": "7.12.1", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-proposal-decorators": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz", - "integrity": "sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.12.1.tgz", + "integrity": "sha512-knNIuusychgYN8fGJHONL0RbFxLGawhXOJNLBk75TniTsZZeA+wdkDuv6wp4lGwzQEKjZi6/WYtnb3udNPmQmQ==", "requires": { - "@babel/helper-create-class-features-plugin": "7.10.5", + "@babel/helper-create-class-features-plugin": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-decorators": "7.10.4" + "@babel/plugin-syntax-decorators": "7.12.1" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", - "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", + "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-dynamic-import": "7.8.3" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", - "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-export-namespace-from": "7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", - "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-json-strings": "7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", - "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-logical-assignment-operators": "7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", - "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", - "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz", + "integrity": "sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-numeric-separator": "7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", - "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@babel/plugin-transform-parameters": "7.10.5" + "@babel/plugin-transform-parameters": "7.12.1" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", - "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/plugin-syntax-optional-catch-binding": "7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", - "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz", + "integrity": "sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "7.11.0", + "@babel/helper-skip-transparent-expression-wrappers": "7.12.1", "@babel/plugin-syntax-optional-chaining": "7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", - "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", "requires": { - "@babel/helper-create-class-features-plugin": "7.10.5", + "@babel/helper-create-class-features-plugin": "7.12.1", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", - "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", "requires": { - "@babel/helper-create-regexp-features-plugin": "7.10.4", + "@babel/helper-create-regexp-features-plugin": "7.12.1", "@babel/helper-plugin-utils": "7.10.4" } }, @@ -480,18 +459,26 @@ "@babel/helper-plugin-utils": "7.10.4" } }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "requires": { + "@babel/helper-plugin-utils": "7.10.4" + } + }, "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-syntax-decorators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz", - "integrity": "sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz", + "integrity": "sha512-ir9YW5daRrTYiy9UJ2TzdNIJEZu8KclVzDcfSt4iEmOtwQ4llPtWInNKJyKnVXp1vE4bbVd5S31M/im3mYMO1w==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } @@ -513,9 +500,17 @@ } }, "@babel/plugin-syntax-flow": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.1.tgz", + "integrity": "sha512-1lBLLmtxrwpm4VKmtVFselI/P3pX+G63fAtUUt6b2Nzgao77KNDwyuRt90Mj2/9pKobtt68FdvjfqohZjg/FCA==", + "requires": { + "@babel/helper-plugin-utils": "7.10.4" + } + }, + "@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.10.4.tgz", - "integrity": "sha512-yxQsX1dJixF4qEEdzVbst3SZQ58Nrooz8NV9Z9GL4byTE25BvJgl5lf0RECUf0fh28rZBb/RYTWn/eeKwCMrZQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } @@ -529,9 +524,9 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", - "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } @@ -585,429 +580,420 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-syntax-typescript": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.4.tgz", - "integrity": "sha512-oSAEz1YkBCAKr5Yiq8/BNtvSAPwkp/IyUnwZogd8p+F0RuYQQrLeRUzIQhueQTTBy/F+a40uS7OFKxnkRvmvFQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz", + "integrity": "sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", - "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", - "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", "requires": { - "@babel/helper-module-imports": "7.10.4", + "@babel/helper-module-imports": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-remap-async-to-generator": "7.11.4" + "@babel/helper-remap-async-to-generator": "7.12.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", - "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", - "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", + "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", - "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", "requires": { "@babel/helper-annotate-as-pure": "7.10.4", "@babel/helper-define-map": "7.10.5", "@babel/helper-function-name": "7.10.4", "@babel/helper-optimise-call-expression": "7.10.4", "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-replace-supers": "7.10.4", + "@babel/helper-replace-supers": "7.12.1", "@babel/helper-split-export-declaration": "7.11.0", "globals": "11.12.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", - "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", - "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", - "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", "requires": { - "@babel/helper-create-regexp-features-plugin": "7.10.4", + "@babel/helper-create-regexp-features-plugin": "7.12.1", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", - "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", - "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", "requires": { "@babel/helper-builder-binary-assignment-operator-visitor": "7.10.4", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-flow-strip-types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz", - "integrity": "sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.12.1.tgz", + "integrity": "sha512-8hAtkmsQb36yMmEtk2JZ9JnVyDSnDOdlB+0nEGzIDLuK4yR3JcEjfuFPYkdEPSh8Id+rAMeBEn+X0iVEyho6Hg==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-flow": "7.10.4" + "@babel/plugin-syntax-flow": "7.12.1" } }, "@babel/plugin-transform-for-of": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", - "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", - "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", "requires": { "@babel/helper-function-name": "7.10.4", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", - "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", - "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", - "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", "requires": { - "@babel/helper-module-transforms": "7.11.0", + "@babel/helper-module-transforms": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", "babel-plugin-dynamic-import-node": "2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", - "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", "requires": { - "@babel/helper-module-transforms": "7.11.0", + "@babel/helper-module-transforms": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-simple-access": "7.10.4", + "@babel/helper-simple-access": "7.12.1", "babel-plugin-dynamic-import-node": "2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", - "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", "requires": { "@babel/helper-hoist-variables": "7.10.4", - "@babel/helper-module-transforms": "7.11.0", + "@babel/helper-module-transforms": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", + "@babel/helper-validator-identifier": "7.10.4", "babel-plugin-dynamic-import-node": "2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", "requires": { - "@babel/helper-module-transforms": "7.11.0", + "@babel/helper-module-transforms": "7.12.1", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", "requires": { - "@babel/helper-create-regexp-features-plugin": "7.10.4" + "@babel/helper-create-regexp-features-plugin": "7.12.1" } }, "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-replace-supers": "7.10.4" + "@babel/helper-replace-supers": "7.12.1" } }, "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", "requires": { - "@babel/helper-get-function-arity": "7.10.4", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-react-constant-elements": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.4.tgz", - "integrity": "sha512-cYmQBW1pXrqBte1raMkAulXmi7rjg3VI6ZLg9QIic8Hq7BtYXaWuZSxsr2siOMI6SWwpxjWfnwhTUrd7JlAV7g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.1.tgz", + "integrity": "sha512-KOHd0tIRLoER+J+8f9DblZDa1fLGPwaaN1DI1TVHuQFOpjHV22C3CUB3obeC4fexHY9nx+fH0hQNvLFFfA1mxA==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz", - "integrity": "sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz", + "integrity": "sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz", - "integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.1.tgz", + "integrity": "sha512-RmKejwnT0T0QzQUzcbP5p1VWlpnP8QHtdhEtLG55ZDQnJNalbF3eeDyu3dnGKvGzFIQiBzFhBYTwvv435p9Xpw==", "requires": { "@babel/helper-builder-react-jsx": "7.10.4", - "@babel/helper-builder-react-jsx-experimental": "7.11.5", + "@babel/helper-builder-react-jsx-experimental": "7.12.4", "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-jsx": "7.10.4" + "@babel/plugin-syntax-jsx": "7.12.1" } }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.11.5.tgz", - "integrity": "sha512-cImAmIlKJ84sDmpQzm4/0q/2xrXlDezQoixy3qoz1NJeZL/8PRon6xZtluvr4H4FzwlDGI5tCcFupMnXGtr+qw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.1.tgz", + "integrity": "sha512-IilcGWdN1yNgEGOrB96jbTplRh+V2Pz1EoEwsKsHfX1a/L40cUYuD71Zepa7C+ujv7kJIxnDftWeZbKNEqZjCQ==", "requires": { - "@babel/helper-builder-react-jsx-experimental": "7.11.5", + "@babel/helper-builder-react-jsx-experimental": "7.12.4", "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-jsx": "7.10.4" + "@babel/plugin-syntax-jsx": "7.12.1" } }, "@babel/plugin-transform-react-jsx-self": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz", - "integrity": "sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz", + "integrity": "sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA==", "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-jsx": "7.10.4" + "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz", - "integrity": "sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz", + "integrity": "sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ==", "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-jsx": "7.10.4" + "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-react-pure-annotations": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz", - "integrity": "sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", + "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", "requires": { "@babel/helper-annotate-as-pure": "7.10.4", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", "requires": { "regenerator-transform": "0.14.5" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-runtime": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz", - "integrity": "sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz", + "integrity": "sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg==", "requires": { - "@babel/helper-module-imports": "7.10.4", + "@babel/helper-module-imports": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", - "resolve": "1.17.0", + "resolve": "1.18.1", "semver": "5.7.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", - "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "7.11.0" + "@babel/helper-skip-transparent-expression-wrappers": "7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz", + "integrity": "sha512-CiUgKQ3AGVk7kveIaPEET1jNDhZZEl1RPMWdTBE1799bdz++SwqDHStmxfCtDfBhQgCl38YRiSnrMuUMZIWSUQ==", "requires": { "@babel/helper-plugin-utils": "7.10.4", "@babel/helper-regex": "7.10.5" } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", "requires": { - "@babel/helper-annotate-as-pure": "7.10.4", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", + "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-typescript": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.11.0.tgz", - "integrity": "sha512-edJsNzTtvb3MaXQwj8403B7mZoGu9ElDJQZOKjGUnvilquxBA3IQoEIOvkX/1O8xfAsnHS/oQhe2w/IXrr+w0w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz", + "integrity": "sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw==", "requires": { - "@babel/helper-create-class-features-plugin": "7.10.5", + "@babel/helper-create-class-features-plugin": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-typescript": "7.10.4" + "@babel/plugin-syntax-typescript": "7.12.1" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", - "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", "requires": { "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "7.10.4", + "@babel/helper-create-regexp-features-plugin": "7.12.1", "@babel/helper-plugin-utils": "7.10.4" } }, "@babel/preset-env": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", - "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz", + "integrity": "sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==", "requires": { - "@babel/compat-data": "7.11.0", - "@babel/helper-compilation-targets": "7.10.4", - "@babel/helper-module-imports": "7.10.4", + "@babel/compat-data": "7.12.1", + "@babel/helper-compilation-targets": "7.12.1", + "@babel/helper-module-imports": "7.12.1", "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-proposal-async-generator-functions": "7.10.5", - "@babel/plugin-proposal-class-properties": "7.10.4", - "@babel/plugin-proposal-dynamic-import": "7.10.4", - "@babel/plugin-proposal-export-namespace-from": "7.10.4", - "@babel/plugin-proposal-json-strings": "7.10.4", - "@babel/plugin-proposal-logical-assignment-operators": "7.11.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "7.10.4", - "@babel/plugin-proposal-numeric-separator": "7.10.4", - "@babel/plugin-proposal-object-rest-spread": "7.11.0", - "@babel/plugin-proposal-optional-catch-binding": "7.10.4", - "@babel/plugin-proposal-optional-chaining": "7.11.0", - "@babel/plugin-proposal-private-methods": "7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "7.10.4", + "@babel/helper-validator-option": "7.12.1", + "@babel/plugin-proposal-async-generator-functions": "7.12.1", + "@babel/plugin-proposal-class-properties": "7.12.1", + "@babel/plugin-proposal-dynamic-import": "7.12.1", + "@babel/plugin-proposal-export-namespace-from": "7.12.1", + "@babel/plugin-proposal-json-strings": "7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.12.1", + "@babel/plugin-proposal-numeric-separator": "7.12.1", + "@babel/plugin-proposal-object-rest-spread": "7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "7.12.1", + "@babel/plugin-proposal-optional-chaining": "7.12.1", + "@babel/plugin-proposal-private-methods": "7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "7.12.1", "@babel/plugin-syntax-async-generators": "7.8.4", - "@babel/plugin-syntax-class-properties": "7.10.4", + "@babel/plugin-syntax-class-properties": "7.12.1", "@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-export-namespace-from": "7.8.3", "@babel/plugin-syntax-json-strings": "7.8.3", @@ -1017,53 +1003,43 @@ "@babel/plugin-syntax-object-rest-spread": "7.8.3", "@babel/plugin-syntax-optional-catch-binding": "7.8.3", "@babel/plugin-syntax-optional-chaining": "7.8.3", - "@babel/plugin-syntax-top-level-await": "7.10.4", - "@babel/plugin-transform-arrow-functions": "7.10.4", - "@babel/plugin-transform-async-to-generator": "7.10.4", - "@babel/plugin-transform-block-scoped-functions": "7.10.4", - "@babel/plugin-transform-block-scoping": "7.11.1", - "@babel/plugin-transform-classes": "7.10.4", - "@babel/plugin-transform-computed-properties": "7.10.4", - "@babel/plugin-transform-destructuring": "7.10.4", - "@babel/plugin-transform-dotall-regex": "7.10.4", - "@babel/plugin-transform-duplicate-keys": "7.10.4", - "@babel/plugin-transform-exponentiation-operator": "7.10.4", - "@babel/plugin-transform-for-of": "7.10.4", - "@babel/plugin-transform-function-name": "7.10.4", - "@babel/plugin-transform-literals": "7.10.4", - "@babel/plugin-transform-member-expression-literals": "7.10.4", - "@babel/plugin-transform-modules-amd": "7.10.5", - "@babel/plugin-transform-modules-commonjs": "7.10.4", - "@babel/plugin-transform-modules-systemjs": "7.10.5", - "@babel/plugin-transform-modules-umd": "7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "7.10.4", - "@babel/plugin-transform-new-target": "7.10.4", - "@babel/plugin-transform-object-super": "7.10.4", - "@babel/plugin-transform-parameters": "7.10.5", - "@babel/plugin-transform-property-literals": "7.10.4", - "@babel/plugin-transform-regenerator": "7.10.4", - "@babel/plugin-transform-reserved-words": "7.10.4", - "@babel/plugin-transform-shorthand-properties": "7.10.4", - "@babel/plugin-transform-spread": "7.11.0", - "@babel/plugin-transform-sticky-regex": "7.10.4", - "@babel/plugin-transform-template-literals": "7.10.5", - "@babel/plugin-transform-typeof-symbol": "7.10.4", - "@babel/plugin-transform-unicode-escapes": "7.10.4", - "@babel/plugin-transform-unicode-regex": "7.10.4", + "@babel/plugin-syntax-top-level-await": "7.12.1", + "@babel/plugin-transform-arrow-functions": "7.12.1", + "@babel/plugin-transform-async-to-generator": "7.12.1", + "@babel/plugin-transform-block-scoped-functions": "7.12.1", + "@babel/plugin-transform-block-scoping": "7.12.1", + "@babel/plugin-transform-classes": "7.12.1", + "@babel/plugin-transform-computed-properties": "7.12.1", + "@babel/plugin-transform-destructuring": "7.12.1", + "@babel/plugin-transform-dotall-regex": "7.12.1", + "@babel/plugin-transform-duplicate-keys": "7.12.1", + "@babel/plugin-transform-exponentiation-operator": "7.12.1", + "@babel/plugin-transform-for-of": "7.12.1", + "@babel/plugin-transform-function-name": "7.12.1", + "@babel/plugin-transform-literals": "7.12.1", + "@babel/plugin-transform-member-expression-literals": "7.12.1", + "@babel/plugin-transform-modules-amd": "7.12.1", + "@babel/plugin-transform-modules-commonjs": "7.12.1", + "@babel/plugin-transform-modules-systemjs": "7.12.1", + "@babel/plugin-transform-modules-umd": "7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "7.12.1", + "@babel/plugin-transform-new-target": "7.12.1", + "@babel/plugin-transform-object-super": "7.12.1", + "@babel/plugin-transform-parameters": "7.12.1", + "@babel/plugin-transform-property-literals": "7.12.1", + "@babel/plugin-transform-regenerator": "7.12.1", + "@babel/plugin-transform-reserved-words": "7.12.1", + "@babel/plugin-transform-shorthand-properties": "7.12.1", + "@babel/plugin-transform-spread": "7.12.1", + "@babel/plugin-transform-sticky-regex": "7.12.1", + "@babel/plugin-transform-template-literals": "7.12.1", + "@babel/plugin-transform-typeof-symbol": "7.12.1", + "@babel/plugin-transform-unicode-escapes": "7.12.1", + "@babel/plugin-transform-unicode-regex": "7.12.1", "@babel/preset-modules": "0.1.4", - "@babel/types": "7.11.5", - "browserslist": "4.14.1", + "@babel/types": "7.12.1", "core-js-compat": "3.6.5", - "invariant": "2.2.4", - "levenary": "1.1.1", "semver": "5.7.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/preset-modules": { @@ -1072,47 +1048,47 @@ "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "7.10.4", - "@babel/plugin-transform-dotall-regex": "7.10.4", - "@babel/types": "7.11.5", + "@babel/plugin-proposal-unicode-property-regex": "7.12.1", + "@babel/plugin-transform-dotall-regex": "7.12.1", + "@babel/types": "7.12.1", "esutils": "2.0.3" } }, "@babel/preset-react": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz", - "integrity": "sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.12.1.tgz", + "integrity": "sha512-euCExymHCi0qB9u5fKw7rvlw7AZSjw/NaB9h7EkdTt5+yHRrXdiRTh7fkG3uBPpJg82CqLfp1LHLqWGSCrab+g==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-transform-react-display-name": "7.10.4", - "@babel/plugin-transform-react-jsx": "7.10.4", - "@babel/plugin-transform-react-jsx-development": "7.11.5", - "@babel/plugin-transform-react-jsx-self": "7.10.4", - "@babel/plugin-transform-react-jsx-source": "7.10.5", - "@babel/plugin-transform-react-pure-annotations": "7.10.4" + "@babel/plugin-transform-react-display-name": "7.12.1", + "@babel/plugin-transform-react-jsx": "7.12.1", + "@babel/plugin-transform-react-jsx-development": "7.12.1", + "@babel/plugin-transform-react-jsx-self": "7.12.1", + "@babel/plugin-transform-react-jsx-source": "7.12.1", + "@babel/plugin-transform-react-pure-annotations": "7.12.1" } }, "@babel/preset-typescript": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz", - "integrity": "sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.1.tgz", + "integrity": "sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-transform-typescript": "7.11.0" + "@babel/plugin-transform-typescript": "7.12.1" } }, "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", + "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", "requires": { "regenerator-runtime": "0.13.7" } }, "@babel/runtime-corejs3": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", - "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.1.tgz", + "integrity": "sha512-umhPIcMrlBZ2aTWlWjUseW9LjQKxi1dpFlQS8DzsxB//5K+u6GLTC/JliPKHsd5kJVPIU6X/Hy0YvWOYPcMxBw==", "requires": { "core-js-pure": "3.6.5", "regenerator-runtime": "0.13.7" @@ -1124,36 +1100,41 @@ "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "requires": { "@babel/code-frame": "7.10.4", - "@babel/parser": "7.11.5", - "@babel/types": "7.11.5" + "@babel/parser": "7.12.3", + "@babel/types": "7.12.1" } }, "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", + "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", "requires": { "@babel/code-frame": "7.10.4", - "@babel/generator": "7.11.6", + "@babel/generator": "7.12.1", "@babel/helper-function-name": "7.10.4", "@babel/helper-split-export-declaration": "7.11.0", - "@babel/parser": "7.11.5", - "@babel/types": "7.11.5", - "debug": "4.1.1", + "@babel/parser": "7.12.3", + "@babel/types": "7.12.1", + "debug": "4.2.0", "globals": "11.12.0", "lodash": "4.17.20" } }, "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", + "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", "requires": { "@babel/helper-validator-identifier": "7.10.4", "lodash": "4.17.20", "to-fast-properties": "2.0.0" } }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + }, "@cnakazawa/watch": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", @@ -1173,6 +1154,38 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, + "@eslint/eslintrc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.0.tgz", + "integrity": "sha512-+cIGPCBdLCzqxdtwppswP+zTsH9BOIGzAeKfBIbtb4gW/giMlfMwP0HUSFfhzh20f9u8uZ8hOp62+4GPquTbwQ==", + "requires": { + "ajv": "6.12.6", + "debug": "4.2.0", + "espree": "7.3.0", + "globals": "12.4.0", + "ignore": "4.0.6", + "import-fresh": "3.2.1", + "js-yaml": "3.14.0", + "lodash": "4.17.20", + "minimatch": "3.0.4", + "strip-json-comments": "3.1.1" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "requires": { + "type-fest": "0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + } + } + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -1207,174 +1220,1005 @@ "@hapi/hoek": "8.5.1" } }, - "@jest/console": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", - "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "requires": { - "@jest/source-map": "24.9.0", - "chalk": "2.4.2", - "slash": "2.0.0" + "camelcase": "5.3.1", + "find-up": "4.1.0", + "get-package-type": "0.1.0", + "js-yaml": "3.14.0", + "resolve-from": "5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==" + }, + "@jest/console": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.1.tgz", + "integrity": "sha512-cjqcXepwC5M+VeIhwT6Xpi/tT4AiNzlIx8SMJ9IihduHnsSrnWNvTBfKIpmqOOCNOPqtbBx6w2JqfoLOJguo8g==", + "requires": { + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "chalk": "4.1.0", + "jest-message-util": "26.6.1", + "jest-util": "26.6.1", + "slash": "3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "@jest/core": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", - "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.1.tgz", + "integrity": "sha512-p4F0pgK3rKnoS9olXXXOkbus1Bsu6fd8pcvLMPsUy4CVXZ8WSeiwQ1lK5hwkCIqJ+amZOYPd778sbPha/S8Srw==", "requires": { - "@jest/console": "24.9.0", - "@jest/reporters": "24.9.0", - "@jest/test-result": "24.9.0", - "@jest/transform": "24.9.0", - "@jest/types": "24.9.0", - "ansi-escapes": "3.2.0", - "chalk": "2.4.2", + "@jest/console": "26.6.1", + "@jest/reporters": "26.6.1", + "@jest/test-result": "26.6.1", + "@jest/transform": "26.6.1", + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "ansi-escapes": "4.3.1", + "chalk": "4.1.0", "exit": "0.1.2", "graceful-fs": "4.2.4", - "jest-changed-files": "24.9.0", - "jest-config": "24.9.0", - "jest-haste-map": "24.9.0", - "jest-message-util": "24.9.0", - "jest-regex-util": "24.9.0", - "jest-resolve": "24.9.0", - "jest-resolve-dependencies": "24.9.0", - "jest-runner": "24.9.0", - "jest-runtime": "24.9.0", - "jest-snapshot": "24.9.0", - "jest-util": "24.9.0", - "jest-validate": "24.9.0", - "jest-watcher": "24.9.0", - "micromatch": "3.1.10", - "p-each-series": "1.0.0", - "realpath-native": "1.1.0", - "rimraf": "2.7.1", - "slash": "2.0.0", - "strip-ansi": "5.2.0" + "jest-changed-files": "26.6.1", + "jest-config": "26.6.1", + "jest-haste-map": "26.6.1", + "jest-message-util": "26.6.1", + "jest-regex-util": "26.0.0", + "jest-resolve": "26.6.1", + "jest-resolve-dependencies": "26.6.1", + "jest-runner": "26.6.1", + "jest-runtime": "26.6.1", + "jest-snapshot": "26.6.1", + "jest-util": "26.6.1", + "jest-validate": "26.6.1", + "jest-watcher": "26.6.1", + "micromatch": "4.0.2", + "p-each-series": "2.1.0", + "rimraf": "3.0.2", + "slash": "3.0.0", + "strip-ansi": "6.0.0" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.1.tgz", + "integrity": "sha512-hiHfQH6rrcpAmw9xCQ0vD66SDuU+7ZulOuKwc4jpbmFFsz0bQG/Ib92K+9/489u5rVw0btr/ZhiHqBpmkbCvuQ==", + "requires": { + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "jest-pnp-resolver": "1.2.2", + "jest-util": "26.6.1", + "read-pkg-up": "7.0.1", + "resolve": "1.18.1", + "slash": "3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "7.10.4", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.1.0", + "type-fest": "0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "7.1.6" + } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" } } } }, "@jest/environment": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", - "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.1.tgz", + "integrity": "sha512-GNvHwkOFJtNgSwdzH9flUPzF9AYAZhUg124CBoQcwcZCM9s5TLz8Y3fMtiaWt4ffbigoetjGk5PU2Dd8nLrSEw==", "requires": { - "@jest/fake-timers": "24.9.0", - "@jest/transform": "24.9.0", - "@jest/types": "24.9.0", - "jest-mock": "24.9.0" + "@jest/fake-timers": "26.6.1", + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "jest-mock": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "@jest/fake-timers": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", - "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.1.tgz", + "integrity": "sha512-T/SkMLgOquenw/nIisBRD6XAYpFir0kNuclYLkse5BpzeDUukyBr+K31xgAo9M0hgjU9ORlekAYPSzc0DKfmKg==", "requires": { - "@jest/types": "24.9.0", - "jest-message-util": "24.9.0", - "jest-mock": "24.9.0" + "@jest/types": "26.6.1", + "@sinonjs/fake-timers": "6.0.1", + "@types/node": "14.14.3", + "jest-message-util": "26.6.1", + "jest-mock": "26.6.1", + "jest-util": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } + } + }, + "@jest/globals": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.1.tgz", + "integrity": "sha512-acxXsSguuLV/CeMYmBseefw6apO7NuXqpE+v5r3yD9ye2PY7h1nS20vY7Obk2w6S7eJO4OIAJeDnoGcLC/McEQ==", + "requires": { + "@jest/environment": "26.6.1", + "@jest/types": "26.6.1", + "expect": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "@jest/reporters": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", - "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.1.tgz", + "integrity": "sha512-J6OlXVFY3q1SXWJhjme5i7qT/BAZSikdOK2t8Ht5OS32BDo6KfG5CzIzzIFnAVd82/WWbc9Hb7SJ/jwSvVH9YA==", "requires": { - "@jest/environment": "24.9.0", - "@jest/test-result": "24.9.0", - "@jest/transform": "24.9.0", - "@jest/types": "24.9.0", - "chalk": "2.4.2", + "@bcoe/v8-coverage": "0.2.3", + "@jest/console": "26.6.1", + "@jest/test-result": "26.6.1", + "@jest/transform": "26.6.1", + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "collect-v8-coverage": "1.0.1", "exit": "0.1.2", "glob": "7.1.6", - "istanbul-lib-coverage": "2.0.5", - "istanbul-lib-instrument": "3.3.0", - "istanbul-lib-report": "2.0.8", - "istanbul-lib-source-maps": "3.0.6", - "istanbul-reports": "2.2.7", - "jest-haste-map": "24.9.0", - "jest-resolve": "24.9.0", - "jest-runtime": "24.9.0", - "jest-util": "24.9.0", - "jest-worker": "24.9.0", - "node-notifier": "5.4.3", - "slash": "2.0.0", + "graceful-fs": "4.2.4", + "istanbul-lib-coverage": "3.0.0", + "istanbul-lib-instrument": "4.0.3", + "istanbul-lib-report": "3.0.0", + "istanbul-lib-source-maps": "4.0.0", + "istanbul-reports": "3.0.2", + "jest-haste-map": "26.6.1", + "jest-resolve": "26.6.1", + "jest-util": "26.6.1", + "jest-worker": "26.6.1", + "node-notifier": "8.0.0", + "slash": "3.0.0", "source-map": "0.6.1", - "string-length": "2.0.0" + "string-length": "4.0.1", + "terminal-link": "2.1.1", + "v8-to-istanbul": "6.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.1.tgz", + "integrity": "sha512-hiHfQH6rrcpAmw9xCQ0vD66SDuU+7ZulOuKwc4jpbmFFsz0bQG/Ib92K+9/489u5rVw0btr/ZhiHqBpmkbCvuQ==", + "requires": { + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "jest-pnp-resolver": "1.2.2", + "jest-util": "26.6.1", + "read-pkg-up": "7.0.1", + "resolve": "1.18.1", + "slash": "3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "7.10.4", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.1.0", + "type-fest": "0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "@jest/source-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", - "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.5.0.tgz", + "integrity": "sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g==", "requires": { "callsites": "3.1.0", "graceful-fs": "4.2.4", "source-map": "0.6.1" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - } } }, "@jest/test-result": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", - "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.1.tgz", + "integrity": "sha512-wqAgIerIN2gSdT2A8WeA5+AFh9XQBqYGf8etK143yng3qYd0mF0ie2W5PVmgnjw4VDU6ammI9NdXrKgNhreawg==", "requires": { - "@jest/console": "24.9.0", - "@jest/types": "24.9.0", - "@types/istanbul-lib-coverage": "2.0.3" + "@jest/console": "26.6.1", + "@jest/types": "26.6.1", + "@types/istanbul-lib-coverage": "2.0.3", + "collect-v8-coverage": "1.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "@jest/test-sequencer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", - "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.1.tgz", + "integrity": "sha512-0csqA/XApZiNeTIPYh6koIDCACSoR6hi29T61tKJMtCZdEC+tF3PoNt7MS0oK/zKC6daBgCbqXxia5ztr/NyCQ==", "requires": { - "@jest/test-result": "24.9.0", - "jest-haste-map": "24.9.0", - "jest-runner": "24.9.0", - "jest-runtime": "24.9.0" + "@jest/test-result": "26.6.1", + "graceful-fs": "4.2.4", + "jest-haste-map": "26.6.1", + "jest-runner": "26.6.1", + "jest-runtime": "26.6.1" } }, "@jest/transform": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", - "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.1.tgz", + "integrity": "sha512-oNFAqVtqRxZRx6vXL3I4bPKUK0BIlEeaalkwxyQGGI8oXDQBtYQBpiMe5F7qPs4QdvvFYB42gPGIMMcxXaBBxQ==", "requires": { - "@babel/core": "7.9.0", - "@jest/types": "24.9.0", - "babel-plugin-istanbul": "5.2.0", - "chalk": "2.4.2", + "@babel/core": "7.12.3", + "@jest/types": "26.6.1", + "babel-plugin-istanbul": "6.0.0", + "chalk": "4.1.0", "convert-source-map": "1.7.0", "fast-json-stable-stringify": "2.1.0", "graceful-fs": "4.2.4", - "jest-haste-map": "24.9.0", - "jest-regex-util": "24.9.0", - "jest-util": "24.9.0", - "micromatch": "3.1.10", + "jest-haste-map": "26.6.1", + "jest-regex-util": "26.0.0", + "jest-util": "26.6.1", + "micromatch": "4.0.2", "pirates": "4.0.1", - "realpath-native": "1.1.0", - "slash": "2.0.0", + "slash": "3.0.0", "source-map": "0.6.1", - "write-file-atomic": "2.4.1" + "write-file-atomic": "3.0.3" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "@jest/types": { @@ -1384,145 +2228,252 @@ "requires": { "@types/istanbul-lib-coverage": "2.0.3", "@types/istanbul-reports": "1.1.2", - "@types/yargs": "13.0.10" + "@types/yargs": "13.0.11" } }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", "requires": { - "call-me-maybe": "1.0.1", - "glob-to-regexp": "0.3.0" + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "1.1.9" } }, "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "1.8.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "requires": { + "mkdirp": "1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } }, "@nymproject/nym-client-wasm": { "version": "file:../pkg" }, + "@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.2.tgz", + "integrity": "sha512-Loc4UDGutcZ+Bd56hBInkm6JyjyCwWy4t2wcDXzN8EDPANgVRj0VP8Nxn0Zq2pc+WKauZwEivQgbDGg4xZO20A==", + "requires": { + "ansi-html": "0.0.7", + "error-stack-parser": "2.0.6", + "html-entities": "1.3.1", + "native-url": "0.2.6", + "schema-utils": "2.7.1", + "source-map": "0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, + "@rollup/plugin-node-resolve": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", + "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", + "requires": { + "@rollup/pluginutils": "3.1.0", + "@types/resolve": "0.0.8", + "builtin-modules": "3.1.0", + "is-module": "1.0.0", + "resolve": "1.18.1" + } + }, + "@rollup/plugin-replace": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.3.tgz", + "integrity": "sha512-XPmVXZ7IlaoWaJLkSCDaa0Y6uVo5XQYHhiMFzOd5qSv5rE+t/UJToPIOE56flKIxBFQI27ONsxb7dqHnwSsjKQ==", + "requires": { + "@rollup/pluginutils": "3.1.0", + "magic-string": "0.25.7" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "1.0.1", + "picomatch": "2.2.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + } + } + }, "@sheerun/mutationobserver-shim": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", "integrity": "sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw==" }, + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "requires": { + "@sinonjs/commons": "1.8.1" + } + }, + "@surma/rollup-plugin-off-main-thread": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.1.tgz", + "integrity": "sha512-ZPBWYQDdO4JZiTmTP3DABsHhIPA7bEJk9Znk7tZsrbPGanoGo8YxMv//WLx5Cvb+lRgS42+6yiOIYYHCKDmkpQ==", + "requires": { + "ejs": "2.7.4", + "magic-string": "0.25.7" + } + }, "@svgr/babel-plugin-add-jsx-attribute": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", - "integrity": "sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==" }, "@svgr/babel-plugin-remove-jsx-attribute": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz", - "integrity": "sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==" }, "@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz", - "integrity": "sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==" }, "@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz", - "integrity": "sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==" }, "@svgr/babel-plugin-svg-dynamic-title": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.3.tgz", - "integrity": "sha512-w3Be6xUNdwgParsvxkkeZb545VhXEwjGMwExMVBIdPQJeyMQHqm9Msnb2a1teHBqUYL66qtwfhNkbj1iarCG7w==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==" }, "@svgr/babel-plugin-svg-em-dimensions": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz", - "integrity": "sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==" }, "@svgr/babel-plugin-transform-react-native-svg": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz", - "integrity": "sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==" }, "@svgr/babel-plugin-transform-svg-component": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz", - "integrity": "sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz", + "integrity": "sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A==" }, "@svgr/babel-preset": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-4.3.3.tgz", - "integrity": "sha512-6PG80tdz4eAlYUN3g5GZiUjg2FMcp+Wn6rtnz5WJG9ITGEF1pmFdzq02597Hn0OmnQuCVaBYQE1OVFAnwOl+0A==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz", + "integrity": "sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A==", "requires": { - "@svgr/babel-plugin-add-jsx-attribute": "4.2.0", - "@svgr/babel-plugin-remove-jsx-attribute": "4.2.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "4.2.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "4.2.0", - "@svgr/babel-plugin-svg-dynamic-title": "4.3.3", - "@svgr/babel-plugin-svg-em-dimensions": "4.2.0", - "@svgr/babel-plugin-transform-react-native-svg": "4.2.0", - "@svgr/babel-plugin-transform-svg-component": "4.2.0" + "@svgr/babel-plugin-add-jsx-attribute": "5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "5.4.0", + "@svgr/babel-plugin-transform-svg-component": "5.4.0" } }, "@svgr/core": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-4.3.3.tgz", - "integrity": "sha512-qNuGF1QON1626UCaZamWt5yedpgOytvLj5BQZe2j1k1B8DUG4OyugZyfEwBeXozCUwhLEpsrgPrE+eCu4fY17w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz", + "integrity": "sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ==", "requires": { - "@svgr/plugin-jsx": "4.3.3", - "camelcase": "5.3.1", - "cosmiconfig": "5.2.1" + "@svgr/plugin-jsx": "5.4.0", + "camelcase": "6.1.0", + "cosmiconfig": "6.0.0" }, "dependencies": { "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==" } } }, "@svgr/hast-util-to-babel-ast": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.3.2.tgz", - "integrity": "sha512-JioXclZGhFIDL3ddn4Kiq8qEqYM2PyDKV0aYno8+IXTLuYt6TOgHUbUAAFvqtb0Xn37NwP0BTHglejFoYr8RZg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz", + "integrity": "sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@svgr/plugin-jsx": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-4.3.3.tgz", - "integrity": "sha512-cLOCSpNWQnDB1/v+SUENHH7a0XY09bfuMKdq9+gYvtuwzC2rU4I0wKGFEp1i24holdQdwodCtDQdFtJiTCWc+w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz", + "integrity": "sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw==", "requires": { - "@babel/core": "7.9.0", - "@svgr/babel-preset": "4.3.3", - "@svgr/hast-util-to-babel-ast": "4.3.2", + "@babel/core": "7.12.3", + "@svgr/babel-preset": "5.4.0", + "@svgr/hast-util-to-babel-ast": "5.4.0", "svg-parser": "2.0.4" } }, "@svgr/plugin-svgo": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-4.3.1.tgz", - "integrity": "sha512-PrMtEDUWjX3Ea65JsVCwTIXuSqa3CG9px+DluF1/eo9mlDrgrtFE7NE/DjdhjJgSM9wenlVBzkzneSIUgfUI/w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz", + "integrity": "sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA==", "requires": { - "cosmiconfig": "5.2.1", + "cosmiconfig": "6.0.0", "merge-deep": "3.0.2", "svgo": "1.3.2" } }, "@svgr/webpack": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-4.3.3.tgz", - "integrity": "sha512-bjnWolZ6KVsHhgyCoYRFmbd26p8XVbulCzSG53BDQqAr+JOAderYK7CuYrB3bDjHJuF6LJ7Wrr42+goLRV9qIg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", + "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", "requires": { - "@babel/core": "7.9.0", - "@babel/plugin-transform-react-constant-elements": "7.10.4", - "@babel/preset-env": "7.11.5", - "@babel/preset-react": "7.10.4", - "@svgr/core": "4.3.3", - "@svgr/plugin-jsx": "4.3.3", - "@svgr/plugin-svgo": "4.3.1", - "loader-utils": "1.4.0" + "@babel/core": "7.12.3", + "@babel/plugin-transform-react-constant-elements": "7.12.1", + "@babel/preset-env": "7.12.1", + "@babel/preset-react": "7.12.1", + "@svgr/core": "5.4.0", + "@svgr/plugin-jsx": "5.4.0", + "@svgr/plugin-svgo": "5.4.0", + "loader-utils": "2.0.0" } }, "@testing-library/dom": { @@ -1530,7 +2481,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.16.0.tgz", "integrity": "sha512-lBD88ssxqEfz0wFL6MeUyyWZfV/2cjEZZV3YRpb2IoJRej/4f1jB0TzqIOznTpfR1r34CNesrubxwIlAQ8zgPA==", "requires": { - "@babel/runtime": "7.11.2", + "@babel/runtime": "7.12.1", "@sheerun/mutationobserver-shim": "0.3.3", "@types/testing-library__dom": "6.14.0", "aria-query": "4.2.2", @@ -1546,14 +2497,14 @@ "requires": { "@types/istanbul-lib-coverage": "2.0.3", "@types/istanbul-reports": "1.1.2", - "@types/yargs": "15.0.5", + "@types/yargs": "15.0.9", "chalk": "3.0.0" } }, "@types/yargs": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", "requires": { "@types/yargs-parser": "15.0.0" } @@ -1564,11 +2515,10 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "@types/color-name": "1.1.1", "color-convert": "2.0.1" } }, @@ -1577,7 +2527,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "requires": { - "ansi-styles": "4.2.1", + "ansi-styles": "4.3.0", "supports-color": "7.2.0" } }, @@ -1606,7 +2556,7 @@ "requires": { "@jest/types": "25.5.0", "ansi-regex": "5.0.0", - "ansi-styles": "4.2.1", + "ansi-styles": "4.3.0", "react-is": "16.13.1" } }, @@ -1625,7 +2575,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz", "integrity": "sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg==", "requires": { - "@babel/runtime": "7.11.2", + "@babel/runtime": "7.12.1", "chalk": "2.4.2", "css": "2.2.4", "css.escape": "1.5.1", @@ -1641,7 +2591,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.5.0.tgz", "integrity": "sha512-di1b+D0p+rfeboHO5W7gTVeZDIK5+maEgstrZbWZSSvxDyfDRkkyBE1AJR5Psd6doNldluXlCWqXriUfqu/9Qg==", "requires": { - "@babel/runtime": "7.11.2", + "@babel/runtime": "7.12.1", "@testing-library/dom": "6.16.0", "@types/testing-library__react": "9.1.3" } @@ -1651,52 +2601,61 @@ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz", "integrity": "sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==" }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" + }, "@types/babel__core": { - "version": "7.1.9", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", - "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.10.tgz", + "integrity": "sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw==", "requires": { - "@babel/parser": "7.11.5", - "@babel/types": "7.11.5", - "@types/babel__generator": "7.6.1", - "@types/babel__template": "7.0.2", - "@types/babel__traverse": "7.0.13" + "@babel/parser": "7.12.3", + "@babel/types": "7.12.1", + "@types/babel__generator": "7.6.2", + "@types/babel__template": "7.0.3", + "@types/babel__traverse": "7.0.15" } }, "@types/babel__generator": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", - "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", + "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.3.tgz", + "integrity": "sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q==", "requires": { - "@babel/parser": "7.11.5", - "@babel/types": "7.11.5" + "@babel/parser": "7.12.3", + "@babel/types": "7.12.1" } }, "@types/babel__traverse": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", - "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.15.tgz", + "integrity": "sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A==", "requires": { - "@babel/types": "7.11.5" + "@babel/types": "7.12.1" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "@types/eslint": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.4.tgz", + "integrity": "sha512-YCY4kzHMsHoyKspQH+nwSe+70Kep7Vjt2X+dZe5Vs2vkRudqtoFoUIv1RlJmZB8Hbp7McneupoZij4PadxsK5Q==", + "requires": { + "@types/estree": "0.0.45", + "@types/json-schema": "7.0.6" + } }, - "@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==" + "@types/estree": { + "version": "0.0.45", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz", + "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==" }, "@types/glob": { "version": "7.1.3", @@ -1704,9 +2663,22 @@ "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "requires": { "@types/minimatch": "3.0.3", - "@types/node": "14.6.4" + "@types/node": "14.14.3" } }, + "@types/graceful-fs": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", + "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", + "requires": { + "@types/node": "14.14.3" + } + }, + "@types/html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -1734,21 +2706,36 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/node": { - "version": "14.6.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", - "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==" + "version": "14.14.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.3.tgz", + "integrity": "sha512-33/L34xS7HVUx23e0wOT2V1qPF1IrHgQccdJVm9uXGTB9vFBrrzBtkQymT8VskeKOxjz55MSqMv0xuLq+u98WQ==" + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/prettier": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", + "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==" + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -1760,12 +2747,12 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" }, "@types/react": { - "version": "16.9.49", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz", - "integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==", + "version": "16.9.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.53.tgz", + "integrity": "sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==", "requires": { "@types/prop-types": "15.7.3", - "csstype": "3.0.3" + "csstype": "3.0.4" } }, "@types/react-dom": { @@ -1773,13 +2760,31 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", "requires": { - "@types/react": "16.9.49" + "@types/react": "16.9.53" } }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "requires": { + "@types/node": "14.14.3" + } + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" + }, "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==" + }, + "@types/tapable": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", + "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==" }, "@types/testing-library__dom": { "version": "6.14.0", @@ -1806,14 +2811,14 @@ "requires": { "@types/istanbul-lib-coverage": "2.0.3", "@types/istanbul-reports": "1.1.2", - "@types/yargs": "15.0.5", + "@types/yargs": "15.0.9", "chalk": "3.0.0" } }, "@types/yargs": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", "requires": { "@types/yargs-parser": "15.0.0" } @@ -1824,11 +2829,10 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "@types/color-name": "1.1.1", "color-convert": "2.0.1" } }, @@ -1837,7 +2841,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "requires": { - "ansi-styles": "4.2.1", + "ansi-styles": "4.3.0", "supports-color": "7.2.0" } }, @@ -1866,7 +2870,7 @@ "requires": { "@jest/types": "25.5.0", "ansi-regex": "5.0.0", - "ansi-styles": "4.2.1", + "ansi-styles": "4.3.0", "react-is": "16.13.1" } }, @@ -1880,10 +2884,48 @@ } } }, + "@types/uglify-js": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.0.tgz", + "integrity": "sha512-I0Yd8TUELTbgRHq2K65j8rnDPAzAP+DiaF/syLem7yXwYLsHZhPd+AM2iXsWmf9P2F2NlFCgl5erZPQx9IbM9Q==", + "requires": { + "source-map": "0.6.1" + } + }, + "@types/webpack": { + "version": "4.41.23", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.23.tgz", + "integrity": "sha512-ojA4CupZg8RCzVJLugWlvqrHpT59GWhqFxbinlsnvk10MjQCWB+ot7XDACctbWhnhtdhYK7+HOH1JxkVLiZhMg==", + "requires": { + "@types/anymatch": "1.3.1", + "@types/node": "14.14.3", + "@types/tapable": "1.0.6", + "@types/uglify-js": "3.11.0", + "@types/webpack-sources": "2.0.0", + "source-map": "0.6.1" + } + }, + "@types/webpack-sources": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.0.0.tgz", + "integrity": "sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg==", + "requires": { + "@types/node": "14.14.3", + "@types/source-list-map": "0.1.2", + "source-map": "0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, "@types/yargs": { - "version": "13.0.10", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz", - "integrity": "sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ==", + "version": "13.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.11.tgz", + "integrity": "sha512-NRqD6T4gktUrDi1o1wLH3EKC1o2caCr7/wR87ODcbVITQF106OM3sFN92ysZ++wqelOd1CTzatnOBRDYYG6wGQ==", "requires": { "@types/yargs-parser": "15.0.0" } @@ -1894,207 +2936,249 @@ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, "@typescript-eslint/eslint-plugin": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz", - "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.6.0.tgz", + "integrity": "sha512-1+419X+Ynijytr1iWI+/IcX/kJryc78YNpdaXR1aRO1sU3bC0vZrIAF1tIX7rudVI84W7o7M4zo5p1aVt70fAg==", "requires": { - "@typescript-eslint/experimental-utils": "2.34.0", + "@typescript-eslint/experimental-utils": "4.6.0", + "@typescript-eslint/scope-manager": "4.6.0", + "debug": "4.2.0", "functional-red-black-tree": "1.0.1", "regexpp": "3.1.0", + "semver": "7.3.2", "tsutils": "3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } } }, "@typescript-eslint/experimental-utils": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", - "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.0.tgz", + "integrity": "sha512-pnh6Beh2/4xjJVNL+keP49DFHk3orDHHFylSp3WEjtgW3y1U+6l+jNnJrGlbs6qhAz5z96aFmmbUyKhunXKvKw==", "requires": { "@types/json-schema": "7.0.6", - "@typescript-eslint/typescript-estree": "2.34.0", - "eslint-scope": "5.1.0", + "@typescript-eslint/scope-manager": "4.6.0", + "@typescript-eslint/types": "4.6.0", + "@typescript-eslint/typescript-estree": "4.6.0", + "eslint-scope": "5.1.1", "eslint-utils": "2.1.0" } }, "@typescript-eslint/parser": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", - "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.6.0.tgz", + "integrity": "sha512-Dj6NJxBhbdbPSZ5DYsQqpR32MwujF772F2H3VojWU6iT4AqL4BKuoNWOPFCoSZvCcADDvQjDpa6OLDAaiZPz2Q==", "requires": { - "@types/eslint-visitor-keys": "1.0.0", - "@typescript-eslint/experimental-utils": "2.34.0", - "@typescript-eslint/typescript-estree": "2.34.0", - "eslint-visitor-keys": "1.3.0" + "@typescript-eslint/scope-manager": "4.6.0", + "@typescript-eslint/types": "4.6.0", + "@typescript-eslint/typescript-estree": "4.6.0", + "debug": "4.2.0" } }, - "@typescript-eslint/typescript-estree": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", - "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", + "@typescript-eslint/scope-manager": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.6.0.tgz", + "integrity": "sha512-uZx5KvStXP/lwrMrfQQwDNvh2ppiXzz5TmyTVHb+5TfZ3sUP7U1onlz3pjoWrK9konRyFe1czyxObWTly27Ang==", "requires": { - "debug": "4.1.1", - "eslint-visitor-keys": "1.3.0", - "glob": "7.1.6", + "@typescript-eslint/types": "4.6.0", + "@typescript-eslint/visitor-keys": "4.6.0" + } + }, + "@typescript-eslint/types": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.6.0.tgz", + "integrity": "sha512-5FAgjqH68SfFG4UTtIFv+rqYJg0nLjfkjD0iv+5O27a0xEeNZ5rZNDvFGZDizlCD1Ifj7MAbSW2DPMrf0E9zjA==" + }, + "@typescript-eslint/typescript-estree": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.0.tgz", + "integrity": "sha512-s4Z9qubMrAo/tw0CbN0IN4AtfwuehGXVZM0CHNMdfYMGBDhPdwTEpBrecwhP7dRJu6d9tT9ECYNaWDHvlFSngA==", + "requires": { + "@typescript-eslint/types": "4.6.0", + "@typescript-eslint/visitor-keys": "4.6.0", + "debug": "4.2.0", + "globby": "11.0.1", "is-glob": "4.0.1", "lodash": "4.17.20", "semver": "7.3.2", "tsutils": "3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.0.tgz", + "integrity": "sha512-38Aa9Ztl0XyFPVzmutHXqDMCu15Xx8yKvUo38Gu3GhsuckCh3StPI5t2WIO9LHEsOH7MLmlGfKUisU8eW1Sjhg==", + "requires": { + "@typescript-eslint/types": "4.6.0", + "eslint-visitor-keys": "2.0.0" } }, "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", "requires": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" }, "@webassemblyjs/helper-api-error": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" }, "@webassemblyjs/helper-buffer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" }, "@webassemblyjs/helper-code-frame": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", "requires": { - "@webassemblyjs/wast-printer": "1.8.5" + "@webassemblyjs/wast-printer": "1.9.0" } }, "@webassemblyjs/helper-fsm": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" }, "@webassemblyjs/helper-module-context": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "mamacro": "0.0.3" + "@webassemblyjs/ast": "1.9.0" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" }, "@webassemblyjs/helper-wasm-section": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" } }, "@webassemblyjs/ieee754": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", "requires": { "@xtuc/ieee754": "1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" }, "@webassemblyjs/wasm-edit": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/helper-wasm-section": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-opt": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "@webassemblyjs/wast-printer": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" } }, "@webassemblyjs/wasm-gen": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, "@webassemblyjs/wasm-opt": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" } }, "@webassemblyjs/wasm-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" } }, "@webassemblyjs/wast-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/floating-point-hex-parser": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-code-frame": "1.8.5", - "@webassemblyjs/helper-fsm": "1.8.5", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", "@xtuc/long": "4.2.2" } }, @@ -2109,9 +3193,9 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "abab": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz", - "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" }, "abbrev": { "version": "1.1.1", @@ -2128,24 +3212,17 @@ } }, "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" }, "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", "requires": { - "acorn": "6.4.1", - "acorn-walk": "6.2.0" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" - } + "acorn": "7.4.1", + "acorn-walk": "7.2.0" } }, "acorn-jsx": { @@ -2154,9 +3231,9 @@ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==" }, "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" }, "address": { "version": "1.1.2", @@ -2164,45 +3241,12 @@ "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==" }, "adjust-sourcemap-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz", - "integrity": "sha512-4hFsTsn58+YjrU9qKzML2JSSDqKvN8mUGQ0nNIrfPi8hmIONT4L3uUaT6MKdMsZ9AjsU6D2xDkZxCkbQPxChrA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", + "integrity": "sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw==", "requires": { - "assert": "1.4.1", - "camelcase": "5.0.0", - "loader-utils": "1.2.3", - "object-path": "0.11.4", - "regex-parser": "2.2.10" - }, - "dependencies": { - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "1.2.5" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "requires": { - "big.js": "5.2.2", - "emojis-list": "2.1.0", - "json5": "1.0.1" - } - } + "loader-utils": "2.0.0", + "regex-parser": "2.2.11" } }, "aggregate-error": { @@ -2215,9 +3259,9 @@ } }, "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "3.1.3", "fast-json-stable-stringify": "2.1.0", @@ -2246,9 +3290,9 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" }, "ansi-escapes": { "version": "4.3.1", @@ -2284,12 +3328,12 @@ } }, "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" + "normalize-path": "3.0.0", + "picomatch": "2.2.2" } }, "aproba": { @@ -2319,8 +3363,8 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "requires": { - "@babel/runtime": "7.11.2", - "@babel/runtime-corejs3": "7.11.2" + "@babel/runtime": "7.12.1", + "@babel/runtime-corejs3": "7.12.1" } }, "arity-n": { @@ -2343,11 +3387,6 @@ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -2364,17 +3403,34 @@ "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6", + "es-abstract": "1.17.7", "is-string": "1.0.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "1.0.3" - } + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, "array-uniq": { "version": "1.0.3", @@ -2392,13 +3448,63 @@ "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6" + "es-abstract": "1.17.7" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } + } + }, + "array.prototype.flatmap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", + "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.17.7", + "function-bind": "1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" }, "asap": { "version": "2.0.6", @@ -2432,11 +3538,27 @@ } }, "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "requires": { + "object-assign": "4.1.1", "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } } }, "assert-plus": { @@ -2487,6 +3609,11 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -2497,12 +3624,12 @@ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", "requires": { - "browserslist": "4.14.1", - "caniuse-lite": "1.0.30001124", + "browserslist": "4.14.5", + "caniuse-lite": "1.0.30001151", "colorette": "1.2.1", "normalize-range": "0.1.2", "num2fraction": "1.2.2", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "4.1.0" } }, @@ -2516,61 +3643,34 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" }, + "axe-core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.0.2.tgz", + "integrity": "sha512-arU1h31OGFu+LPrOLGZ7nB45v940NMDMEJeNmbutu57P+UFDVnkZg3e+J1I2HJRZ9hT7gO8J91dn/PMrAiKakA==" + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.3", - "js-tokens": "3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, "babel-eslint": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "requires": { "@babel/code-frame": "7.10.4", - "@babel/parser": "7.11.5", - "@babel/traverse": "7.11.5", - "@babel/types": "7.11.5", + "@babel/parser": "7.12.3", + "@babel/traverse": "7.12.1", + "@babel/types": "7.12.1", "eslint-visitor-keys": "1.3.0", - "resolve": "1.17.0" + "resolve": "1.18.1" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } } }, "babel-extract-comments": { @@ -2582,17 +3682,91 @@ } }, "babel-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", - "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.1.tgz", + "integrity": "sha512-duMWEOKrSBYRVTTNpL2SipNIWnZOjP77auOBMPQ3zXAdnDbyZQWU8r/RxNWpUf9N6cgPFecQYelYLytTVXVDtA==", "requires": { - "@jest/transform": "24.9.0", - "@jest/types": "24.9.0", - "@types/babel__core": "7.1.9", - "babel-plugin-istanbul": "5.2.0", - "babel-preset-jest": "24.9.0", - "chalk": "2.4.2", - "slash": "2.0.0" + "@jest/transform": "26.6.1", + "@jest/types": "26.6.1", + "@types/babel__core": "7.1.10", + "babel-plugin-istanbul": "6.0.0", + "babel-preset-jest": "26.5.0", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "slash": "3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "babel-loader": { @@ -2607,6 +3781,24 @@ "schema-utils": "2.7.1" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -2619,36 +3811,30 @@ "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "requires": { - "object.assign": "4.1.0" + "object.assign": "4.1.1" } }, "babel-plugin-istanbul": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", - "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", "requires": { "@babel/helper-plugin-utils": "7.10.4", - "find-up": "3.0.0", - "istanbul-lib-instrument": "3.3.0", - "test-exclude": "5.2.3" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "3.0.0" - } - } + "@istanbuljs/load-nyc-config": "1.1.0", + "@istanbuljs/schema": "0.1.2", + "istanbul-lib-instrument": "4.0.3", + "test-exclude": "6.0.0" } }, "babel-plugin-jest-hoist": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", - "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz", + "integrity": "sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw==", "requires": { - "@types/babel__traverse": "7.0.13" + "@babel/template": "7.10.4", + "@babel/types": "7.12.1", + "@types/babel__core": "7.1.10", + "@types/babel__traverse": "7.0.15" } }, "babel-plugin-macros": { @@ -2656,59 +3842,15 @@ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", "requires": { - "@babel/runtime": "7.11.2", + "@babel/runtime": "7.12.1", "cosmiconfig": "6.0.0", - "resolve": "1.17.0" - }, - "dependencies": { - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "4.0.0", - "import-fresh": "3.2.1", - "parse-json": "5.1.0", - "path-type": "4.0.0", - "yaml": "1.10.0" - } - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "requires": { - "parent-module": "1.0.1", - "resolve-from": "4.0.0" - } - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "requires": { - "@babel/code-frame": "7.10.4", - "error-ex": "1.3.2", - "json-parse-even-better-errors": "2.3.1", - "lines-and-columns": "1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - } + "resolve": "1.18.1" } }, "babel-plugin-named-asset-import": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz", - "integrity": "sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA==" + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", + "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==" }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", @@ -2729,174 +3871,53 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" }, - "babel-preset-jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", - "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "babel-preset-current-node-syntax": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz", + "integrity": "sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w==", "requires": { + "@babel/plugin-syntax-async-generators": "7.8.4", + "@babel/plugin-syntax-bigint": "7.8.3", + "@babel/plugin-syntax-class-properties": "7.12.1", + "@babel/plugin-syntax-import-meta": "7.10.4", + "@babel/plugin-syntax-json-strings": "7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "7.8.3", + "@babel/plugin-syntax-numeric-separator": "7.10.4", "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "babel-plugin-jest-hoist": "24.9.0" + "@babel/plugin-syntax-optional-catch-binding": "7.8.3", + "@babel/plugin-syntax-optional-chaining": "7.8.3" + } + }, + "babel-preset-jest": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz", + "integrity": "sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA==", + "requires": { + "babel-plugin-jest-hoist": "26.5.0", + "babel-preset-current-node-syntax": "0.1.4" } }, "babel-preset-react-app": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-9.1.2.tgz", - "integrity": "sha512-k58RtQOKH21NyKtzptoAvtAODuAJJs3ZhqBMl456/GnXEQ/0La92pNmwgWoMn5pBTrsvk3YYXdY7zpY4e3UIxA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.0.tgz", + "integrity": "sha512-itL2z8v16khpuKutx5IH8UdCdSTuzrOhRFTEdIhveZ2i1iBKDrVE0ATa4sFVy+02GLucZNVBWtoarXBy0Msdpg==", "requires": { - "@babel/core": "7.9.0", - "@babel/plugin-proposal-class-properties": "7.8.3", - "@babel/plugin-proposal-decorators": "7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3", - "@babel/plugin-proposal-numeric-separator": "7.8.3", - "@babel/plugin-proposal-optional-chaining": "7.9.0", - "@babel/plugin-transform-flow-strip-types": "7.9.0", - "@babel/plugin-transform-react-display-name": "7.8.3", - "@babel/plugin-transform-runtime": "7.9.0", - "@babel/preset-env": "7.9.0", - "@babel/preset-react": "7.9.1", - "@babel/preset-typescript": "7.9.0", - "@babel/runtime": "7.9.0", + "@babel/core": "7.12.3", + "@babel/plugin-proposal-class-properties": "7.12.1", + "@babel/plugin-proposal-decorators": "7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.12.1", + "@babel/plugin-proposal-numeric-separator": "7.12.1", + "@babel/plugin-proposal-optional-chaining": "7.12.1", + "@babel/plugin-transform-flow-strip-types": "7.12.1", + "@babel/plugin-transform-react-display-name": "7.12.1", + "@babel/plugin-transform-runtime": "7.12.1", + "@babel/preset-env": "7.12.1", + "@babel/preset-react": "7.12.1", + "@babel/preset-typescript": "7.12.1", + "@babel/runtime": "7.12.1", "babel-plugin-macros": "2.8.0", "babel-plugin-transform-react-remove-prop-types": "0.4.24" - }, - "dependencies": { - "@babel/plugin-proposal-class-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", - "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", - "requires": { - "@babel/helper-create-class-features-plugin": "7.10.5", - "@babel/helper-plugin-utils": "7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-numeric-separator": "7.10.4" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-syntax-optional-chaining": "7.8.3" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", - "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4" - } - }, - "@babel/preset-env": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", - "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", - "requires": { - "@babel/compat-data": "7.11.0", - "@babel/helper-compilation-targets": "7.10.4", - "@babel/helper-module-imports": "7.10.4", - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-proposal-async-generator-functions": "7.10.5", - "@babel/plugin-proposal-dynamic-import": "7.10.4", - "@babel/plugin-proposal-json-strings": "7.10.4", - "@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3", - "@babel/plugin-proposal-numeric-separator": "7.8.3", - "@babel/plugin-proposal-object-rest-spread": "7.11.0", - "@babel/plugin-proposal-optional-catch-binding": "7.10.4", - "@babel/plugin-proposal-optional-chaining": "7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "7.10.4", - "@babel/plugin-syntax-async-generators": "7.8.4", - "@babel/plugin-syntax-dynamic-import": "7.8.3", - "@babel/plugin-syntax-json-strings": "7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "7.8.3", - "@babel/plugin-syntax-numeric-separator": "7.10.4", - "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "7.8.3", - "@babel/plugin-syntax-optional-chaining": "7.8.3", - "@babel/plugin-syntax-top-level-await": "7.10.4", - "@babel/plugin-transform-arrow-functions": "7.10.4", - "@babel/plugin-transform-async-to-generator": "7.10.4", - "@babel/plugin-transform-block-scoped-functions": "7.10.4", - "@babel/plugin-transform-block-scoping": "7.11.1", - "@babel/plugin-transform-classes": "7.10.4", - "@babel/plugin-transform-computed-properties": "7.10.4", - "@babel/plugin-transform-destructuring": "7.10.4", - "@babel/plugin-transform-dotall-regex": "7.10.4", - "@babel/plugin-transform-duplicate-keys": "7.10.4", - "@babel/plugin-transform-exponentiation-operator": "7.10.4", - "@babel/plugin-transform-for-of": "7.10.4", - "@babel/plugin-transform-function-name": "7.10.4", - "@babel/plugin-transform-literals": "7.10.4", - "@babel/plugin-transform-member-expression-literals": "7.10.4", - "@babel/plugin-transform-modules-amd": "7.10.5", - "@babel/plugin-transform-modules-commonjs": "7.10.4", - "@babel/plugin-transform-modules-systemjs": "7.10.5", - "@babel/plugin-transform-modules-umd": "7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "7.10.4", - "@babel/plugin-transform-new-target": "7.10.4", - "@babel/plugin-transform-object-super": "7.10.4", - "@babel/plugin-transform-parameters": "7.10.5", - "@babel/plugin-transform-property-literals": "7.10.4", - "@babel/plugin-transform-regenerator": "7.10.4", - "@babel/plugin-transform-reserved-words": "7.10.4", - "@babel/plugin-transform-shorthand-properties": "7.10.4", - "@babel/plugin-transform-spread": "7.11.0", - "@babel/plugin-transform-sticky-regex": "7.10.4", - "@babel/plugin-transform-template-literals": "7.10.5", - "@babel/plugin-transform-typeof-symbol": "7.10.4", - "@babel/plugin-transform-unicode-regex": "7.10.4", - "@babel/preset-modules": "0.1.4", - "@babel/types": "7.11.5", - "browserslist": "4.14.1", - "core-js-compat": "3.6.5", - "invariant": "2.2.4", - "levenary": "1.1.1", - "semver": "5.7.1" - } - }, - "@babel/preset-react": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.1.tgz", - "integrity": "sha512-aJBYF23MPj0RNdp/4bHnAP0NVqqZRr9kl0NAOP4nJCex6OYVio59+dnQzsAWFuogdLyeaKA1hmfUIVZkY5J+TQ==", - "requires": { - "@babel/helper-plugin-utils": "7.10.4", - "@babel/plugin-transform-react-display-name": "7.8.3", - "@babel/plugin-transform-react-jsx": "7.10.4", - "@babel/plugin-transform-react-jsx-development": "7.11.5", - "@babel/plugin-transform-react-jsx-self": "7.10.4", - "@babel/plugin-transform-react-jsx-source": "7.10.5" - } - }, - "@babel/runtime": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.0.tgz", - "integrity": "sha512-cTIudHnzuWLS56ik4DnRnqqNf8MkdUzV4iFFI1h7Jo9xvrpQROYaAnaSd2mHLQAzzZAPfATynX5ord6YlNYNMA==", - "requires": { - "regenerator-runtime": "0.13.7" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "babel-runtime": { @@ -3003,6 +4024,17 @@ "tweetnacl": "0.14.5" } }, + "bfj": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", + "integrity": "sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw==", + "requires": { + "bluebird": "3.7.2", + "check-types": "11.1.2", + "hoopy": "0.1.4", + "tryer": "1.0.1" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -3011,7 +4043,8 @@ "binary-extensions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "optional": true }, "block-stream": { "version": "0.0.9", @@ -3092,9 +4125,9 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "bootstrap": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.2.tgz", - "integrity": "sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A==" + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.3.tgz", + "integrity": "sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ==" }, "brace-expansion": { "version": "1.1.11", @@ -3106,30 +4139,11 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.3", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "0.1.1" - } - } + "fill-range": "7.0.1" } }, "brorand": { @@ -3142,21 +4156,6 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" - } - } - }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -3249,14 +4248,14 @@ } }, "browserslist": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.1.tgz", - "integrity": "sha512-zyBTIHydW37pnb63c7fHFXUG6EcqWOqoMdDx6cdyaDFriZ20EoVxcE95S54N+heRqY8m8IUgB5zYta/gCwSaaA==", + "version": "4.14.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", + "integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==", "requires": { - "caniuse-lite": "1.0.30001124", - "electron-to-chromium": "1.3.564", - "escalade": "3.0.2", - "node-releases": "1.1.60" + "caniuse-lite": "1.0.30001151", + "electron-to-chromium": "1.3.583", + "escalade": "3.1.1", + "node-releases": "1.1.64" } }, "bser": { @@ -3292,6 +4291,11 @@ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==" + }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -3303,42 +4307,67 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", "requires": { - "chownr": "1.1.4", - "figgy-pudding": "3.5.2", + "@npmcli/move-file": "1.0.1", + "chownr": "2.0.0", "fs-minipass": "2.1.0", "glob": "7.1.6", - "graceful-fs": "4.2.4", "infer-owner": "1.0.4", - "lru-cache": "5.1.1", + "lru-cache": "6.0.0", "minipass": "3.1.3", "minipass-collect": "1.0.2", "minipass-flush": "1.0.5", "minipass-pipeline": "1.2.4", - "mkdirp": "0.5.5", - "move-concurrently": "1.0.1", - "p-map": "3.0.0", + "mkdirp": "1.0.4", + "p-map": "4.0.0", "promise-inflight": "1.0.1", - "rimraf": "2.7.1", - "ssri": "7.1.0", + "rimraf": "3.0.2", + "ssri": "8.0.0", + "tar": "6.0.5", "unique-filename": "1.1.1" }, "dependencies": { "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { - "yallist": "3.1.1" + "yallist": "4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "7.1.6" + } + }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "requires": { + "chownr": "2.0.0", + "fs-minipass": "2.1.0", + "minipass": "3.1.3", + "minizlib": "2.1.2", + "mkdirp": "1.0.4", + "yallist": "4.0.0" } }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -3358,17 +4387,19 @@ "unset-value": "1.0.0" } }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" - }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "requires": { "callsites": "2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + } } }, "caller-path": { @@ -3380,9 +4411,9 @@ } }, "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camel-case": { "version": "4.1.1", @@ -3390,7 +4421,7 @@ "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", "requires": { "pascal-case": "3.1.1", - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "camelcase": { @@ -3412,16 +4443,16 @@ "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "requires": { - "browserslist": "4.14.1", - "caniuse-lite": "1.0.30001124", + "browserslist": "4.14.5", + "caniuse-lite": "1.0.30001151", "lodash.memoize": "4.1.2", "lodash.uniq": "4.5.0" } }, "caniuse-lite": { - "version": "1.0.30001124", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz", - "integrity": "sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA==" + "version": "1.0.30001151", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001151.tgz", + "integrity": "sha512-Zh3sHqskX6mHNrqUerh+fkf0N72cMxrmflzje/JyVImfpknscMnkeJrlFGJcqTmaa0iszdYptGpWMJCRQDkBVw==" }, "capture-exit": { "version": "2.0.0", @@ -3451,82 +4482,48 @@ "supports-color": "5.5.0" } }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "check-types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", + "integrity": "sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==" + }, "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "optional": true, "requires": { "anymatch": "3.1.1", "braces": "3.0.2", - "fsevents": "2.1.2", + "fsevents": "2.1.3", "glob-parent": "5.1.1", "is-binary-path": "2.1.0", "is-glob": "4.0.1", "normalize-path": "3.0.0", - "readdirp": "3.4.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "requires": { - "normalize-path": "3.0.0", - "picomatch": "2.2.2" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "7.0.0" - } - } + "readdirp": "3.5.0" } }, "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "requires": { - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "ci-info": { @@ -3543,6 +4540,11 @@ "safe-buffer": "5.1.2" } }, + "cjs-module-lexer": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.4.3.tgz", + "integrity": "sha512-5RLK0Qfs0PNDpEyBXIr3bIT1Muw3ojSlvpw6dAmkUcO0+uTrsBn7GuEIgx40u+OzbCBLDta7nvmud85P4EmTsQ==" + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -3662,6 +4664,11 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -3672,12 +4679,12 @@ } }, "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", + "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", "requires": { "color-convert": "1.9.3", - "color-string": "1.5.3" + "color-string": "1.5.4" } }, "color-convert": { @@ -3694,9 +4701,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", "requires": { "color-name": "1.1.3", "simple-swizzle": "0.2.2" @@ -3716,9 +4723,9 @@ } }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" }, "common-tags": { "version": "1.8.0", @@ -3797,9 +4804,9 @@ } }, "confusing-browser-globals": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", - "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", + "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==" }, "connect-history-api-fallback": { "version": "1.6.0", @@ -3895,6 +4902,15 @@ "webpack-log": "2.0.0" }, "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, "cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -3918,6 +4934,21 @@ "y18n": "4.0.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "3.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -3946,7 +4977,7 @@ "dev": true, "requires": { "array-union": "1.0.2", - "dir-glob": "2.0.0", + "dir-glob": "2.2.2", "glob": "7.1.6", "ignore": "3.3.10", "pify": "3.0.0", @@ -3959,6 +4990,26 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3968,11 +5019,14 @@ "yallist": "3.1.1" } }, - "normalize-path": { + "path-type": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "3.0.0" + } }, "pify": { "version": "3.0.0", @@ -3986,7 +5040,7 @@ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "ajv": "6.12.4", + "ajv": "6.12.6", "ajv-errors": "1.0.1", "ajv-keywords": "3.5.2" } @@ -4033,7 +5087,7 @@ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", "requires": { - "browserslist": "4.14.1", + "browserslist": "4.14.5", "semver": "7.0.0" }, "dependencies": { @@ -4055,24 +5109,32 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "requires": { - "import-fresh": "2.0.0", - "is-directory": "0.3.1", - "js-yaml": "3.14.0", - "parse-json": "4.0.0" + "@types/parse-json": "4.0.0", + "import-fresh": "3.2.1", + "parse-json": "5.1.0", + "path-type": "4.0.0", + "yaml": "1.10.0" }, "dependencies": { "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "requires": { + "@babel/code-frame": "7.10.4", "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2" + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" } } }, @@ -4153,6 +5215,11 @@ "randomfill": "1.0.4" } }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, "css": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", @@ -4169,7 +5236,7 @@ "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "css-color-names": { @@ -4182,7 +5249,7 @@ "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "timsort": "0.3.0" } }, @@ -4191,7 +5258,7 @@ "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-selector-parser": "5.0.0" }, "dependencies": { @@ -4213,33 +5280,33 @@ } }, "css-loader": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.2.tgz", - "integrity": "sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", + "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==", "requires": { - "camelcase": "5.3.1", + "camelcase": "6.1.0", "cssesc": "3.0.0", "icss-utils": "4.1.1", - "loader-utils": "1.4.0", - "normalize-path": "3.0.0", - "postcss": "7.0.32", + "loader-utils": "2.0.0", + "postcss": "7.0.35", "postcss-modules-extract-imports": "2.0.0", "postcss-modules-local-by-default": "3.0.3", "postcss-modules-scope": "2.2.0", "postcss-modules-values": "3.0.0", "postcss-value-parser": "4.1.0", - "schema-utils": "2.7.1" + "schema-utils": "2.7.1", + "semver": "7.3.2" }, "dependencies": { "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==" }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" } } }, @@ -4248,7 +5315,7 @@ "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "css-select": { @@ -4257,7 +5324,7 @@ "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", "requires": { "boolbase": "1.0.0", - "css-what": "3.3.0", + "css-what": "3.4.2", "domutils": "1.7.0", "nth-check": "1.0.2" } @@ -4277,9 +5344,9 @@ } }, "css-what": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", - "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" }, "css.escape": { "version": "1.5.1", @@ -4304,7 +5371,43 @@ "cosmiconfig": "5.2.1", "cssnano-preset-default": "4.0.7", "is-resolvable": "1.1.0", - "postcss": "7.0.32" + "postcss": "7.0.35" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "2.0.0", + "is-directory": "0.3.1", + "js-yaml": "3.14.0", + "parse-json": "4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "2.0.0", + "resolve-from": "3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } } }, "cssnano-preset-default": { @@ -4314,8 +5417,8 @@ "requires": { "css-declaration-sorter": "4.0.1", "cssnano-util-raw-cache": "4.0.1", - "postcss": "7.0.32", - "postcss-calc": "7.0.4", + "postcss": "7.0.35", + "postcss-calc": "7.0.5", "postcss-colormin": "4.0.3", "postcss-convert-values": "4.0.1", "postcss-discard-comments": "4.0.2", @@ -4359,7 +5462,7 @@ "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "cssnano-util-same-parent": { @@ -4392,22 +5495,29 @@ } }, "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" }, "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "requires": { "cssom": "0.3.8" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + } } }, "csstype": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", - "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz", + "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==" }, "currently-unhandled": { "version": "0.4.1", @@ -4445,31 +5555,19 @@ } }, "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", "requires": { - "abab": "2.0.4", + "abab": "2.0.5", "whatwg-mimetype": "2.3.0", - "whatwg-url": "7.1.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "requires": { - "lodash.sortby": "4.7.0", - "tr46": "1.0.1", - "webidl-conversions": "4.0.2" - } - } + "whatwg-url": "8.4.0" } }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "requires": { "ms": "2.1.2" } @@ -4479,11 +5577,21 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -4492,7 +5600,7 @@ "is-arguments": "1.0.4", "is-date-object": "1.0.2", "is-regex": "1.1.1", - "object-is": "1.1.2", + "object-is": "1.1.3", "object-keys": "1.1.1", "regexp.prototype.flags": "1.3.0" } @@ -4502,6 +5610,11 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -4575,6 +5688,14 @@ "rimraf": "2.7.1" }, "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "1.0.3" + } + }, "globby": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", @@ -4642,9 +5763,9 @@ "dev": true }, "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" }, "detect-node": { "version": "2.0.4", @@ -4698,26 +5819,17 @@ } }, "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "requires": { - "arrify": "1.0.1", - "path-type": "3.0.0" + "path-type": "4.0.0" }, "dependencies": { "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" } } }, @@ -4769,7 +5881,7 @@ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", "requires": { - "@babel/runtime": "7.11.2" + "@babel/runtime": "7.12.1" } }, "dom-serializer": { @@ -4777,14 +5889,14 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", "requires": { - "domelementtype": "2.0.1", - "entities": "2.0.3" + "domelementtype": "2.0.2", + "entities": "2.1.0" }, "dependencies": { "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==" } } }, @@ -4799,11 +5911,18 @@ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" }, "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", "requires": { - "webidl-conversions": "4.0.2" + "webidl-conversions": "5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } } }, "domhandler": { @@ -4829,7 +5948,7 @@ "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", "requires": { "no-case": "3.0.3", - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "dot-prop": { @@ -4880,10 +5999,15 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" + }, "electron-to-chromium": { - "version": "1.3.564", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.564.tgz", - "integrity": "sha512-fNaYN3EtKQWLQsrKXui8mzcryJXuA0LbCLoizeX6oayG2emBaS5MauKjCPAvc29NEY4FpLHIUWiP+Y0Bfrs5dg==" + "version": "1.3.583", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.583.tgz", + "integrity": "sha512-L9BwLwJohjZW9mQESI79HRzhicPk1DFgM+8hOCfGgGCFEcA3Otpv7QK6SGtYoZvfQfE3wKLh0Hd5ptqUFv3gvQ==" }, "elliptic": { "version": "6.5.3", @@ -4906,6 +6030,11 @@ } } }, + "emittery": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", + "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==" + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -4950,10 +6079,18 @@ } } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "requires": { + "ansi-colors": "4.1.1" + } + }, "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" }, "errno": { "version": "0.1.7", @@ -4971,22 +6108,31 @@ "is-arrayish": "0.2.1" } }, + "error-stack-parser": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "requires": { + "stackframe": "1.2.0" + } + }, "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "requires": { "es-to-primitive": "1.2.1", "function-bind": "1.1.1", "has": "1.0.3", "has-symbols": "1.0.1", - "is-callable": "1.2.0", + "is-callable": "1.2.2", + "is-negative-zero": "2.0.0", "is-regex": "1.1.1", "object-inspect": "1.8.0", "object-keys": "1.1.1", - "object.assign": "4.1.0", - "string.prototype.trimend": "1.0.1", - "string.prototype.trimstart": "1.0.1" + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" } }, "es-to-primitive": { @@ -4994,7 +6140,7 @@ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "requires": { - "is-callable": "1.2.0", + "is-callable": "1.2.2", "is-date-object": "1.0.2", "is-symbol": "1.0.3" } @@ -5029,9 +6175,9 @@ } }, "escalade": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", - "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-html": { "version": "1.0.3", @@ -5053,23 +6199,62 @@ "esutils": "2.0.3", "optionator": "0.8.3", "source-map": "0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "word-wrap": "1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "1.1.2" + } + } } }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.0.tgz", + "integrity": "sha512-n5pEU27DRxCSlOhJ2rO57GDLcNsxO0LPpAbpFdh7xmcDmjmlGUfoyrsB3I7yYdQXO5N3gkSTiDrPSPNFiiirXA==", "requires": { "@babel/code-frame": "7.10.4", - "ajv": "6.12.4", - "chalk": "2.4.2", - "cross-spawn": "6.0.5", - "debug": "4.1.1", + "@eslint/eslintrc": "0.2.0", + "ajv": "6.12.6", + "chalk": "4.1.0", + "cross-spawn": "7.0.3", + "debug": "4.2.0", "doctrine": "3.0.0", - "eslint-scope": "5.1.0", - "eslint-utils": "1.4.3", - "eslint-visitor-keys": "1.3.0", - "espree": "6.2.1", + "enquirer": "2.3.6", + "eslint-scope": "5.1.1", + "eslint-utils": "2.1.0", + "eslint-visitor-keys": "2.0.0", + "espree": "7.3.0", "esquery": "1.3.1", "esutils": "2.0.3", "file-entry-cache": "5.0.1", @@ -5079,51 +6264,67 @@ "ignore": "4.0.6", "import-fresh": "3.2.1", "imurmurhash": "0.1.4", - "inquirer": "7.3.3", "is-glob": "4.0.1", "js-yaml": "3.14.0", "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", + "levn": "0.4.1", "lodash": "4.17.20", "minimatch": "3.0.4", - "mkdirp": "0.5.5", "natural-compare": "1.4.0", - "optionator": "0.8.3", + "optionator": "0.9.1", "progress": "2.0.3", - "regexpp": "2.0.1", - "semver": "6.3.0", - "strip-ansi": "5.2.0", + "regexpp": "3.1.0", + "semver": "7.3.2", + "strip-ansi": "6.0.0", "strip-json-comments": "3.1.1", "table": "5.4.6", "text-table": "0.2.0", "v8-compile-cache": "2.1.1" }, "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.7.1", - "shebang-command": "1.2.0", - "which": "1.3.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } + "color-convert": "2.0.1" } }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { - "eslint-visitor-keys": "1.3.0" + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "globals": { @@ -5134,46 +6335,71 @@ "type-fest": "0.8.1" } }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "requires": { - "parent-module": "1.0.1", - "resolve-from": "4.0.0" - } - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" - }, - "resolve-from": { + "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "2.0.0" } } } }, "eslint-config-react-app": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz", - "integrity": "sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", + "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", "requires": { - "confusing-browser-globals": "1.0.9" + "confusing-browser-globals": "1.0.10" } }, "eslint-import-resolver-node": { @@ -5182,7 +6408,7 @@ "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "requires": { "debug": "2.6.9", - "resolve": "1.17.0" + "resolve": "1.18.1" }, "dependencies": { "debug": { @@ -5200,18 +6426,6 @@ } } }, - "eslint-loader": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-3.0.3.tgz", - "integrity": "sha512-+YRqB95PnNvxNp1HEjQmvf9KNvCin5HXYYseOXVC2U0KEcw4IkQ2IQEBG46j7+gW39bMzeu0GsUhVbBY3Votpw==", - "requires": { - "fs-extra": "8.1.0", - "loader-fs-cache": "1.0.3", - "loader-utils": "1.4.0", - "object-hash": "2.0.3", - "schema-utils": "2.7.1" - } - }, "eslint-module-utils": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", @@ -5288,17 +6502,18 @@ } }, "eslint-plugin-flowtype": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz", - "integrity": "sha512-W5hLjpFfZyZsXfo5anlu7HM970JBDqbEshAJUkeczP6BFCIfJXuiIBQXyberLRtOStT0OGPF8efeTbxlHk4LpQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.2.0.tgz", + "integrity": "sha512-z7ULdTxuhlRJcEe1MVljePXricuPOrsWfScRXFhNzVD5dmTHWjIF57AxD0e7AbEoLSbjSsaA5S+hCg43WvpXJQ==", "requires": { - "lodash": "4.17.20" + "lodash": "4.17.20", + "string-natural-compare": "3.0.1" } }, "eslint-plugin-import": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", - "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", "requires": { "array-includes": "3.1.1", "array.prototype.flat": "1.2.3", @@ -5311,7 +6526,8 @@ "minimatch": "3.0.4", "object.values": "1.1.1", "read-pkg-up": "2.0.0", - "resolve": "1.17.0" + "resolve": "1.18.1", + "tsconfig-paths": "3.9.0" }, "dependencies": { "debug": { @@ -5424,50 +6640,55 @@ } } }, - "eslint-plugin-jsx-a11y": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", - "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "eslint-plugin-jest": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.0.tgz", + "integrity": "sha512-827YJ+E8B9PvXu/0eiVSNFfxxndbKv+qE/3GSMhdorCaeaOehtqHGX2YDW9B85TEOre9n/zscledkFW/KbnyGg==", "requires": { - "@babel/runtime": "7.11.2", - "aria-query": "3.0.0", + "@typescript-eslint/experimental-utils": "4.6.0" + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", + "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", + "requires": { + "@babel/runtime": "7.12.1", + "aria-query": "4.2.2", "array-includes": "3.1.1", "ast-types-flow": "0.0.7", + "axe-core": "4.0.2", "axobject-query": "2.2.0", "damerau-levenshtein": "1.0.6", - "emoji-regex": "7.0.3", + "emoji-regex": "9.2.0", "has": "1.0.3", - "jsx-ast-utils": "2.4.1" + "jsx-ast-utils": "3.1.0", + "language-tags": "1.0.5" }, "dependencies": { - "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", - "requires": { - "ast-types-flow": "0.0.7", - "commander": "2.20.3" - } + "emoji-regex": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.0.tgz", + "integrity": "sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==" } } }, "eslint-plugin-react": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", - "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz", + "integrity": "sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==", "requires": { "array-includes": "3.1.1", + "array.prototype.flatmap": "1.2.3", "doctrine": "2.1.0", "has": "1.0.3", - "jsx-ast-utils": "2.4.1", + "jsx-ast-utils": "3.1.0", "object.entries": "1.1.2", "object.fromentries": "2.0.2", "object.values": "1.1.1", "prop-types": "15.7.2", - "resolve": "1.17.0", - "semver": "6.3.0", - "string.prototype.matchall": "4.0.2", - "xregexp": "4.3.0" + "resolve": "1.18.1", + "string.prototype.matchall": "4.0.2" }, "dependencies": { "doctrine": { @@ -5477,23 +6698,78 @@ "requires": { "esutils": "2.0.3" } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, "eslint-plugin-react-hooks": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", - "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", + "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==" + }, + "eslint-plugin-testing-library": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.9.2.tgz", + "integrity": "sha512-79oWT8dIPerbm4fdZj/QkeKB43P3XgSNbBWLnBi+Li0n+CFEvW078Q962VWeXXqHHofuXJeVOXg7grjiw849BQ==", + "requires": { + "@typescript-eslint/experimental-utils": "3.10.1" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "requires": { + "@types/json-schema": "7.0.6", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "5.1.1", + "eslint-utils": "2.1.0" + } + }, + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==" + }, + "@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "requires": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "4.2.0", + "glob": "7.1.6", + "is-glob": "4.0.1", + "lodash": "4.17.20", + "semver": "7.3.2", + "tsutils": "3.17.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "requires": { + "eslint-visitor-keys": "1.3.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } + } }, "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "requires": { "esrecurse": "4.3.0", "estraverse": "4.3.0" @@ -5505,21 +6781,47 @@ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "requires": { "eslint-visitor-keys": "1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } } }, "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==" + }, + "eslint-webpack-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.1.0.tgz", + "integrity": "sha512-WZT1uoJXSwtEJTkS+81XBERFJzNh0xoZn8fUtQNQWri7++UiYaLJjxJTmwEEyI58NJ536upq9tjN9i3jMwkWQg==", + "requires": { + "@types/eslint": "7.2.4", + "arrify": "2.0.1", + "fs-extra": "9.0.1", + "micromatch": "4.0.2", + "schema-utils": "2.7.1" + } }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", "requires": { - "acorn": "7.4.0", + "acorn": "7.4.1", "acorn-jsx": "5.3.1", "eslint-visitor-keys": "1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } } }, "esprima": { @@ -5562,6 +6864,11 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5629,11 +6936,6 @@ "shebang-command": "1.2.0", "which": "1.3.1" } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -5697,16 +6999,142 @@ } }, "expect": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", - "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.1.tgz", + "integrity": "sha512-BRfxIBHagghMmr1D2MRY0Qv5d3Nc8HCqgbDwNXw/9izmM5eBb42a2YjLKSbsqle76ozGkAEPELQX4IdNHAKRNA==", "requires": { - "@jest/types": "24.9.0", - "ansi-styles": "3.2.1", - "jest-get-type": "24.9.0", - "jest-matcher-utils": "24.9.0", - "jest-message-util": "24.9.0", - "jest-regex-util": "24.9.0" + "@jest/types": "26.6.1", + "ansi-styles": "4.3.0", + "jest-get-type": "26.3.0", + "jest-matcher-utils": "26.6.1", + "jest-message-util": "26.6.1", + "jest-regex-util": "26.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "diff-sequences": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz", + "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-diff": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.1.tgz", + "integrity": "sha512-BBNy/zin2m4kG5In126O8chOBxLLS/XMTuuM2+YhgyHk87ewPzKTuTJcqj3lOWOi03NNgrl+DkMeV/exdvG9gg==", + "requires": { + "chalk": "4.1.0", + "diff-sequences": "26.5.0", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "jest-matcher-utils": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.1.tgz", + "integrity": "sha512-9iu3zrsYlUnl8pByhREF9rr5eYoiEb1F7ymNKg6lJr/0qD37LWS5FSW/JcoDl8UdMX2+zAzabDs7sTO+QFKjCg==", + "requires": { + "chalk": "4.1.0", + "jest-diff": "26.6.1", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "express": { @@ -5895,37 +7323,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", "requires": { - "@mrmlnc/readdir-enhanced": "2.2.1", - "@nodelib/fs.stat": "1.1.3", - "glob-parent": "3.1.0", - "is-glob": "4.0.1", + "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.walk": "1.2.4", + "glob-parent": "5.1.1", "merge2": "1.4.1", - "micromatch": "3.1.10" - }, - "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "2.1.1" - } - } - } - } + "micromatch": "4.0.2", + "picomatch": "2.2.2" } }, "fast-json-stable-stringify": { @@ -5938,12 +7345,20 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "requires": { + "reusify": "1.0.4" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", "requires": { - "websocket-driver": "0.7.4" + "websocket-driver": "0.6.5" } }, "fb-watchman": { @@ -5976,38 +7391,37 @@ } }, "file-loader": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.3.0.tgz", - "integrity": "sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz", + "integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==", "requires": { - "loader-utils": "1.4.0", - "schema-utils": "2.7.1" + "loader-utils": "2.0.0", + "schema-utils": "3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "7.0.6", + "ajv": "6.12.6", + "ajv-keywords": "3.5.2" + } + } } }, "filesize": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", - "integrity": "sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", + "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==" }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "0.1.1" - } - } + "to-regex-range": "5.0.1" } }, "finalhandler": { @@ -6068,6 +7482,117 @@ "is-glob": "4.0.1", "micromatch": "3.1.10", "resolve-dir": "1.0.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + } } }, "flat-cache": { @@ -6133,13 +7658,12 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "fork-ts-checker-webpack-plugin": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz", - "integrity": "sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", + "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", "requires": { - "babel-code-frame": "6.26.0", + "@babel/code-frame": "7.10.4", "chalk": "2.4.2", - "chokidar": "3.4.2", "micromatch": "3.1.10", "minimatch": "3.0.4", "semver": "5.7.1", @@ -6147,10 +7671,105 @@ "worker-rpc": "0.1.1" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } } } }, @@ -6192,13 +7811,14 @@ } }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "requires": { + "at-least-node": "1.0.0", "graceful-fs": "4.2.4", - "jsonfile": "4.0.0", - "universalify": "0.1.2" + "jsonfile": "6.0.1", + "universalify": "1.0.0" } }, "fs-minipass": { @@ -6226,9 +7846,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "optional": true }, "fstream": { @@ -6290,6 +7910,11 @@ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -6337,11 +7962,6 @@ "is-glob": "4.0.1" } }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" - }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -6373,34 +7993,16 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", "requires": { - "array-union": "1.0.2", - "dir-glob": "2.0.0", - "fast-glob": "2.2.7", - "glob": "7.1.6", - "ignore": "3.3.10", - "pify": "3.0.0", - "slash": "1.0.0" - }, - "dependencies": { - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - } + "array-union": "2.1.0", + "dir-glob": "3.0.1", + "fast-glob": "3.2.4", + "ignore": "5.1.8", + "merge2": "1.4.1", + "slash": "3.0.0" } }, "globule": { @@ -6421,7 +8023,8 @@ "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "optional": true }, "gud": { "version": "1.0.0", @@ -6459,7 +8062,7 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { - "ajv": "6.12.4", + "ajv": "6.12.6", "har-schema": "2.0.0" } }, @@ -6525,6 +8128,24 @@ "kind-of": "4.0.0" }, "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -6606,6 +8227,11 @@ "parse-passwd": "1.0.0" } }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==" + }, "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", @@ -6638,9 +8264,9 @@ "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" }, "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", "requires": { "whatwg-encoding": "1.0.5" } @@ -6667,28 +8293,42 @@ "param-case": "3.0.3", "relateurl": "0.2.7", "terser": "4.8.0" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - } } }, "html-webpack-plugin": { - "version": "4.0.0-beta.11", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz", - "integrity": "sha512-4Xzepf0qWxf8CGg7/WQM5qBB2Lc/NFI7MhU59eUDTkuQp3skZczH4UA1d6oQyDEIoMDgERVhRyTdtUPZ5s5HBg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz", + "integrity": "sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==", "requires": { + "@types/html-minifier-terser": "5.1.1", + "@types/tapable": "1.0.6", + "@types/webpack": "4.41.23", "html-minifier-terser": "5.1.1", "loader-utils": "1.4.0", "lodash": "4.17.20", - "pretty-error": "2.1.1", + "pretty-error": "2.1.2", "tapable": "1.1.3", "util.promisify": "1.0.0" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, "util.promisify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", @@ -6754,11 +8394,6 @@ } } }, - "http-parser-js": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", - "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" - }, "http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -6778,6 +8413,108 @@ "is-glob": "4.0.1", "lodash": "4.17.20", "micromatch": "3.1.10" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + } } }, "http-signature": { @@ -6795,6 +8532,11 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6808,7 +8550,7 @@ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "identity-obj-proxy": { @@ -6830,14 +8572,14 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "immer": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", - "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", + "integrity": "sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==" }, "import-cwd": { "version": "2.1.0", @@ -6848,12 +8590,12 @@ } }, "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "requires": { - "caller-path": "2.0.0", - "resolve-from": "3.0.0" + "parent-module": "1.0.1", + "resolve-from": "4.0.0" } }, "import-from": { @@ -6862,15 +8604,62 @@ "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", "requires": { "resolve-from": "3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } } }, "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", "requires": { - "pkg-dir": "3.0.0", - "resolve-cwd": "2.0.0" + "pkg-dir": "4.2.0", + "resolve-cwd": "3.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "4.1.0" + } + } } }, "imurmurhash": { @@ -6943,11 +8732,10 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "@types/color-name": "1.1.1", "color-convert": "2.0.1" } }, @@ -6956,7 +8744,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { - "ansi-styles": "4.2.1", + "ansi-styles": "4.3.0", "supports-color": "7.2.0" } }, @@ -7030,9 +8818,29 @@ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", "requires": { - "es-abstract": "1.17.6", + "es-abstract": "1.17.7", "has": "1.0.3", "side-channel": "1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "interpret": { @@ -7041,19 +8849,6 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "1.4.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" - }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -7096,6 +8891,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "optional": true, "requires": { "binary-extensions": "2.1.0" } @@ -7106,9 +8902,9 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" }, "is-ci": { "version": "2.0.0", @@ -7131,6 +8927,14 @@ "rgba-regex": "1.0.0" } }, + "is-core-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", + "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", + "requires": { + "has": "1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -7207,18 +9011,20 @@ "is-extglob": "2.1.1" } }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" + }, "is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "3.2.2" - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-obj": { "version": "2.0.0", @@ -7259,6 +9065,11 @@ "isobject": "3.0.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" + }, "is-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", @@ -7324,9 +9135,12 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "2.1.1" + } }, "isarray": { "version": "1.0.0", @@ -7349,21 +9163,18 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==" }, "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "requires": { - "@babel/generator": "7.11.6", - "@babel/parser": "7.11.5", - "@babel/template": "7.10.4", - "@babel/traverse": "7.11.5", - "@babel/types": "7.11.5", - "istanbul-lib-coverage": "2.0.5", + "@babel/core": "7.12.3", + "@istanbuljs/schema": "0.1.2", + "istanbul-lib-coverage": "3.0.0", "semver": "6.3.0" }, "dependencies": { @@ -7375,108 +9186,796 @@ } }, "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "requires": { - "istanbul-lib-coverage": "2.0.5", - "make-dir": "2.1.0", - "supports-color": "6.1.0" + "istanbul-lib-coverage": "3.0.0", + "make-dir": "3.1.0", + "supports-color": "7.2.0" }, "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "requires": { - "has-flag": "3.0.0" + "semver": "6.3.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" } } } }, "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "requires": { - "debug": "4.1.1", - "istanbul-lib-coverage": "2.0.5", - "make-dir": "2.1.0", - "rimraf": "2.7.1", + "debug": "4.2.0", + "istanbul-lib-coverage": "3.0.0", "source-map": "0.6.1" } }, "istanbul-reports": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", - "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "requires": { - "html-escaper": "2.0.2" + "html-escaper": "2.0.2", + "istanbul-lib-report": "3.0.0" } }, "jest": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", - "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", + "version": "26.6.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.0.tgz", + "integrity": "sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA==", "requires": { - "import-local": "2.0.0", - "jest-cli": "24.9.0" + "@jest/core": "26.6.1", + "import-local": "3.0.2", + "jest-cli": "26.6.1" }, "dependencies": { - "jest-cli": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", - "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", "requires": { - "@jest/core": "24.9.0", - "@jest/test-result": "24.9.0", - "@jest/types": "24.9.0", - "chalk": "2.4.2", + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "4.2.0", + "strip-ansi": "6.0.0", + "wrap-ansi": "6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "jest-cli": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.1.tgz", + "integrity": "sha512-aPLoEjlwFrCWhiPpW5NUxQA1X1kWsAnQcQ0SO/fHsCvczL3W75iVAcH9kP6NN+BNqZcHNEvkhxT5cDmBfEAh+w==", + "requires": { + "@jest/core": "26.6.1", + "@jest/test-result": "26.6.1", + "@jest/types": "26.6.1", + "chalk": "4.1.0", "exit": "0.1.2", - "import-local": "2.0.0", + "graceful-fs": "4.2.4", + "import-local": "3.0.2", "is-ci": "2.0.0", - "jest-config": "24.9.0", - "jest-util": "24.9.0", - "jest-validate": "24.9.0", - "prompts": "2.3.2", - "realpath-native": "1.1.0", - "yargs": "13.3.2" + "jest-config": "26.6.1", + "jest-util": "26.6.1", + "jest-validate": "26.6.1", + "prompts": "2.4.0", + "yargs": "15.4.1" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "8.0.0", + "is-fullwidth-code-point": "3.0.0", + "strip-ansi": "6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "4.3.0", + "string-width": "4.2.0", + "strip-ansi": "6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "6.0.0", + "decamelize": "1.2.0", + "find-up": "4.1.0", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "4.2.0", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "18.1.3" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "5.3.1", + "decamelize": "1.2.0" } } } }, "jest-changed-files": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", - "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.1.tgz", + "integrity": "sha512-NhSdZ5F6b/rIN5V46x1l31vrmukD/bJUXgYAY8VtP1SknYdJwjYDRxuLt7Z8QryIdqCjMIn2C0Cd98EZ4umo8Q==", "requires": { - "@jest/types": "24.9.0", - "execa": "1.0.0", - "throat": "4.1.0" + "@jest/types": "26.6.1", + "execa": "4.0.3", + "throat": "5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" + } + }, + "execa": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "requires": { + "cross-spawn": "7.0.3", + "get-stream": "5.2.0", + "human-signals": "1.1.1", + "is-stream": "2.0.0", + "merge-stream": "2.0.0", + "npm-run-path": "4.0.1", + "onetime": "5.1.2", + "signal-exit": "3.0.3", + "strip-final-newline": "2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "3.1.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "2.0.0" + } + } + } + }, + "jest-circus": { + "version": "26.6.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.0.tgz", + "integrity": "sha512-L2/Y9szN6FJPWFK8kzWXwfp+FOR7xq0cUL4lIsdbIdwz3Vh6P1nrpcqOleSzr28zOtSHQNV9Z7Tl+KkuK7t5Ng==", + "requires": { + "@babel/traverse": "7.12.1", + "@jest/environment": "26.6.1", + "@jest/test-result": "26.6.1", + "@jest/types": "26.6.1", + "@types/babel__traverse": "7.0.15", + "@types/node": "14.14.3", + "chalk": "4.1.0", + "co": "4.6.0", + "dedent": "0.7.0", + "expect": "26.6.1", + "is-generator-fn": "2.1.0", + "jest-each": "26.6.1", + "jest-matcher-utils": "26.6.1", + "jest-message-util": "26.6.1", + "jest-runner": "26.6.1", + "jest-runtime": "26.6.1", + "jest-snapshot": "26.6.1", + "jest-util": "26.6.1", + "pretty-format": "26.6.1", + "stack-utils": "2.0.2", + "throat": "5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "diff-sequences": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz", + "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-diff": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.1.tgz", + "integrity": "sha512-BBNy/zin2m4kG5In126O8chOBxLLS/XMTuuM2+YhgyHk87ewPzKTuTJcqj3lOWOi03NNgrl+DkMeV/exdvG9gg==", + "requires": { + "chalk": "4.1.0", + "diff-sequences": "26.5.0", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "jest-matcher-utils": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.1.tgz", + "integrity": "sha512-9iu3zrsYlUnl8pByhREF9rr5eYoiEb1F7ymNKg6lJr/0qD37LWS5FSW/JcoDl8UdMX2+zAzabDs7sTO+QFKjCg==", + "requires": { + "chalk": "4.1.0", + "jest-diff": "26.6.1", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-config": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", - "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.1.tgz", + "integrity": "sha512-mtJzIynIwW1d1nMlKCNCQiSgWaqFn8cH/fOSNY97xG7Y9tBCZbCSuW2GTX0RPmceSJGO7l27JgwC18LEg0Vg+g==", "requires": { - "@babel/core": "7.9.0", - "@jest/test-sequencer": "24.9.0", - "@jest/types": "24.9.0", - "babel-jest": "24.9.0", - "chalk": "2.4.2", + "@babel/core": "7.12.3", + "@jest/test-sequencer": "26.6.1", + "@jest/types": "26.6.1", + "babel-jest": "26.6.1", + "chalk": "4.1.0", + "deepmerge": "4.2.2", "glob": "7.1.6", - "jest-environment-jsdom": "24.9.0", - "jest-environment-node": "24.9.0", - "jest-get-type": "24.9.0", - "jest-jasmine2": "24.9.0", - "jest-regex-util": "24.9.0", - "jest-resolve": "24.9.0", - "jest-util": "24.9.0", - "jest-validate": "24.9.0", - "micromatch": "3.1.10", - "pretty-format": "24.9.0", - "realpath-native": "1.1.0" + "graceful-fs": "4.2.4", + "jest-environment-jsdom": "26.6.1", + "jest-environment-node": "26.6.1", + "jest-get-type": "26.3.0", + "jest-jasmine2": "26.6.1", + "jest-regex-util": "26.0.0", + "jest-resolve": "26.6.1", + "jest-util": "26.6.1", + "jest-validate": "26.6.1", + "micromatch": "4.0.2", + "pretty-format": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "jest-resolve": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.1.tgz", + "integrity": "sha512-hiHfQH6rrcpAmw9xCQ0vD66SDuU+7ZulOuKwc4jpbmFFsz0bQG/Ib92K+9/489u5rVw0btr/ZhiHqBpmkbCvuQ==", + "requires": { + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "jest-pnp-resolver": "1.2.2", + "jest-util": "26.6.1", + "read-pkg-up": "7.0.1", + "resolve": "1.18.1", + "slash": "3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "7.10.4", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.1.0", + "type-fest": "0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-diff": { @@ -7491,124 +9990,295 @@ } }, "jest-docblock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", - "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", "requires": { - "detect-newline": "2.1.0" + "detect-newline": "3.1.0" } }, "jest-each": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", - "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.1.tgz", + "integrity": "sha512-gSn8eB3buchuq45SU7pLB7qmCGax1ZSxfaWuEFblCyNMtyokYaKFh9dRhYPujK6xYL57dLIPhLKatjmB5XWzGA==", "requires": { - "@jest/types": "24.9.0", - "chalk": "2.4.2", - "jest-get-type": "24.9.0", - "jest-util": "24.9.0", - "pretty-format": "24.9.0" + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "jest-get-type": "26.3.0", + "jest-util": "26.6.1", + "pretty-format": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-environment-jsdom": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", - "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.1.tgz", + "integrity": "sha512-A17RiXuHYNVlkM+3QNcQ6n5EZyAc6eld8ra9TW26luounGWpku4tj03uqRgHJCI1d4uHr5rJiuCH5JFRtdmrcA==", "requires": { - "@jest/environment": "24.9.0", - "@jest/fake-timers": "24.9.0", - "@jest/types": "24.9.0", - "jest-mock": "24.9.0", - "jest-util": "24.9.0", - "jsdom": "11.12.0" - } - }, - "jest-environment-jsdom-fourteen": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom-fourteen/-/jest-environment-jsdom-fourteen-1.0.1.tgz", - "integrity": "sha512-DojMX1sY+at5Ep+O9yME34CdidZnO3/zfPh8UW+918C5fIZET5vCjfkegixmsi7AtdYfkr4bPlIzmWnlvQkP7Q==", - "requires": { - "@jest/environment": "24.9.0", - "@jest/fake-timers": "24.9.0", - "@jest/types": "24.9.0", - "jest-mock": "24.9.0", - "jest-util": "24.9.0", - "jsdom": "14.1.0" + "@jest/environment": "26.6.1", + "@jest/fake-timers": "26.6.1", + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "jest-mock": "26.6.1", + "jest-util": "26.6.1", + "jsdom": "16.4.0" }, "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" - }, - "jsdom": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-14.1.0.tgz", - "integrity": "sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng==", + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", "requires": { - "abab": "2.0.4", - "acorn": "6.4.1", - "acorn-globals": "4.3.4", - "array-equal": "1.0.0", - "cssom": "0.3.8", - "cssstyle": "1.4.0", - "data-urls": "1.1.0", - "domexception": "1.0.1", - "escodegen": "1.14.3", - "html-encoding-sniffer": "1.0.2", - "nwsapi": "2.2.0", - "parse5": "5.1.0", - "pn": "1.1.0", - "request": "2.88.2", - "request-promise-native": "1.0.9", - "saxes": "3.1.11", - "symbol-tree": "3.2.4", - "tough-cookie": "2.5.0", - "w3c-hr-time": "1.0.2", - "w3c-xmlserializer": "1.1.2", - "webidl-conversions": "4.0.2", - "whatwg-encoding": "1.0.5", - "whatwg-mimetype": "2.3.0", - "whatwg-url": "7.1.0", - "ws": "6.2.1", - "xml-name-validator": "3.0.0" + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" } }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", "requires": { - "lodash.sortby": "4.7.0", - "tr46": "1.0.1", - "webidl-conversions": "4.0.2" + "@types/istanbul-lib-report": "3.0.0" } }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", "requires": { - "async-limiter": "1.0.1" + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" } } } }, "jest-environment-node": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", - "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.1.tgz", + "integrity": "sha512-YffaCp6h0j1kbcf1NVZ7umC6CPgD67YS+G1BeornfuSkx5s3xdhuwG0DCxSiHPXyT81FfJzA1L7nXvhq50OWIg==", "requires": { - "@jest/environment": "24.9.0", - "@jest/fake-timers": "24.9.0", - "@jest/types": "24.9.0", - "jest-mock": "24.9.0", - "jest-util": "24.9.0" + "@jest/environment": "26.6.1", + "@jest/fake-timers": "26.6.1", + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "jest-mock": "26.6.1", + "jest-util": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-get-type": { @@ -7617,65 +10287,356 @@ "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==" }, "jest-haste-map": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", - "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.1.tgz", + "integrity": "sha512-9kPafkv0nX6ta1PrshnkiyhhoQoFWncrU/uUBt3/AP1r78WSCU5iLceYRTwDvJl67H3RrXqSlSVDDa/AsUB7OQ==", "requires": { - "@jest/types": "24.9.0", - "anymatch": "2.0.0", + "@jest/types": "26.6.1", + "@types/graceful-fs": "4.1.4", + "@types/node": "14.14.3", + "anymatch": "3.1.1", "fb-watchman": "2.0.1", - "fsevents": "1.2.13", + "fsevents": "2.1.3", "graceful-fs": "4.2.4", - "invariant": "2.2.4", - "jest-serializer": "24.9.0", - "jest-util": "24.9.0", - "jest-worker": "24.9.0", - "micromatch": "3.1.10", + "jest-regex-util": "26.0.0", + "jest-serializer": "26.5.0", + "jest-util": "26.6.1", + "jest-worker": "26.6.1", + "micromatch": "4.0.2", "sane": "4.1.0", "walker": "1.0.7" }, "dependencies": { - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", "requires": { - "nan": "2.14.1" + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" } } } }, "jest-jasmine2": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", - "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.1.tgz", + "integrity": "sha512-2uYdT32o/ZzSxYAPduAgokO8OlAL1YdG/9oxcEY138EDNpIK5XRRJDaGzTZdIBWSxk0aR8XxN44FvfXtHB+Fiw==", "requires": { - "@babel/traverse": "7.11.5", - "@jest/environment": "24.9.0", - "@jest/test-result": "24.9.0", - "@jest/types": "24.9.0", - "chalk": "2.4.2", + "@babel/traverse": "7.12.1", + "@jest/environment": "26.6.1", + "@jest/source-map": "26.5.0", + "@jest/test-result": "26.6.1", + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "chalk": "4.1.0", "co": "4.6.0", - "expect": "24.9.0", + "expect": "26.6.1", "is-generator-fn": "2.1.0", - "jest-each": "24.9.0", - "jest-matcher-utils": "24.9.0", - "jest-message-util": "24.9.0", - "jest-runtime": "24.9.0", - "jest-snapshot": "24.9.0", - "jest-util": "24.9.0", - "pretty-format": "24.9.0", - "throat": "4.1.0" + "jest-each": "26.6.1", + "jest-matcher-utils": "26.6.1", + "jest-message-util": "26.6.1", + "jest-runtime": "26.6.1", + "jest-snapshot": "26.6.1", + "jest-util": "26.6.1", + "pretty-format": "26.6.1", + "throat": "5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "diff-sequences": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz", + "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-diff": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.1.tgz", + "integrity": "sha512-BBNy/zin2m4kG5In126O8chOBxLLS/XMTuuM2+YhgyHk87ewPzKTuTJcqj3lOWOi03NNgrl+DkMeV/exdvG9gg==", + "requires": { + "chalk": "4.1.0", + "diff-sequences": "26.5.0", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "jest-matcher-utils": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.1.tgz", + "integrity": "sha512-9iu3zrsYlUnl8pByhREF9rr5eYoiEb1F7ymNKg6lJr/0qD37LWS5FSW/JcoDl8UdMX2+zAzabDs7sTO+QFKjCg==", + "requires": { + "chalk": "4.1.0", + "jest-diff": "26.6.1", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-leak-detector": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", - "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.1.tgz", + "integrity": "sha512-j9ZOtJSJKlHjrs4aIxWjiQUjyrffPdiAQn2Iw0916w7qZE5Lk0T2KhIH6E9vfhzP6sw0Q0jtnLLb4vQ71o1HlA==", "requires": { - "jest-get-type": "24.9.0", - "pretty-format": "24.9.0" + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-matcher-utils": { @@ -7690,26 +10651,173 @@ } }, "jest-message-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", - "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.1.tgz", + "integrity": "sha512-cqM4HnqncIebBNdTKrBoWR/4ufHTll0pK/FWwX0YasK+TlBQEMqw3IEdynuuOTjDPFO3ONlFn37280X48beByw==", "requires": { "@babel/code-frame": "7.10.4", - "@jest/test-result": "24.9.0", - "@jest/types": "24.9.0", - "@types/stack-utils": "1.0.1", - "chalk": "2.4.2", - "micromatch": "3.1.10", - "slash": "2.0.0", - "stack-utils": "1.0.2" + "@jest/types": "26.6.1", + "@types/stack-utils": "2.0.0", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "micromatch": "4.0.2", + "slash": "3.0.0", + "stack-utils": "2.0.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-mock": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", - "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.1.tgz", + "integrity": "sha512-my0lPTBu1awY8iVG62sB2sx9qf8zxNDVX+5aFgoB8Vbqjb6LqIOsfyFA8P1z6H2IsqMbvOX9oCJnK67Y3yUIMA==", "requires": { - "@jest/types": "24.9.0" + "@jest/types": "26.6.1", + "@types/node": "14.14.3" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-pnp-resolver": { @@ -7718,247 +10826,1345 @@ "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" }, "jest-regex-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", - "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==" + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==" }, "jest-resolve": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", - "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", + "version": "26.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.0.tgz", + "integrity": "sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ==", "requires": { - "@jest/types": "24.9.0", - "browser-resolve": "1.11.3", - "chalk": "2.4.2", + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", "jest-pnp-resolver": "1.2.2", - "realpath-native": "1.1.0" + "jest-util": "26.6.1", + "read-pkg-up": "7.0.1", + "resolve": "1.18.1", + "slash": "3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "7.10.4", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.1.0", + "type-fest": "0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-resolve-dependencies": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", - "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.1.tgz", + "integrity": "sha512-MN6lufbZJ3RBfTnJesZtHu3hUCBqPdHRe2+FhIt0yiqJ3fMgzWRqMRQyN/d/QwOE7KXwAG2ekZutbPhuD7s51A==", "requires": { - "@jest/types": "24.9.0", - "jest-regex-util": "24.9.0", - "jest-snapshot": "24.9.0" + "@jest/types": "26.6.1", + "jest-regex-util": "26.0.0", + "jest-snapshot": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-runner": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", - "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.1.tgz", + "integrity": "sha512-DmpNGdgsbl5s0FGkmsInmqnmqCtliCSnjWA2TFAJS1m1mL5atwfPsf+uoZ8uYQ2X0uDj4NM+nPcDnUpbNTRMBA==", "requires": { - "@jest/console": "24.9.0", - "@jest/environment": "24.9.0", - "@jest/test-result": "24.9.0", - "@jest/types": "24.9.0", - "chalk": "2.4.2", + "@jest/console": "26.6.1", + "@jest/environment": "26.6.1", + "@jest/test-result": "26.6.1", + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "chalk": "4.1.0", + "emittery": "0.7.2", "exit": "0.1.2", "graceful-fs": "4.2.4", - "jest-config": "24.9.0", - "jest-docblock": "24.9.0", - "jest-haste-map": "24.9.0", - "jest-jasmine2": "24.9.0", - "jest-leak-detector": "24.9.0", - "jest-message-util": "24.9.0", - "jest-resolve": "24.9.0", - "jest-runtime": "24.9.0", - "jest-util": "24.9.0", - "jest-worker": "24.9.0", + "jest-config": "26.6.1", + "jest-docblock": "26.0.0", + "jest-haste-map": "26.6.1", + "jest-leak-detector": "26.6.1", + "jest-message-util": "26.6.1", + "jest-resolve": "26.6.1", + "jest-runtime": "26.6.1", + "jest-util": "26.6.1", + "jest-worker": "26.6.1", "source-map-support": "0.5.19", - "throat": "4.1.0" + "throat": "5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-resolve": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.1.tgz", + "integrity": "sha512-hiHfQH6rrcpAmw9xCQ0vD66SDuU+7ZulOuKwc4jpbmFFsz0bQG/Ib92K+9/489u5rVw0btr/ZhiHqBpmkbCvuQ==", + "requires": { + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "jest-pnp-resolver": "1.2.2", + "jest-util": "26.6.1", + "read-pkg-up": "7.0.1", + "resolve": "1.18.1", + "slash": "3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "7.10.4", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.1.0", + "type-fest": "0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } } }, "jest-runtime": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", - "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.1.tgz", + "integrity": "sha512-7uOCNeezXDWgjEyzYbRN2ViY7xNZzusNVGAMmU0UHRUNXuY4j4GBHKGMqPo/cBPZA9bSYp+lwK2DRRBU5Dv6YQ==", "requires": { - "@jest/console": "24.9.0", - "@jest/environment": "24.9.0", - "@jest/source-map": "24.9.0", - "@jest/transform": "24.9.0", - "@jest/types": "24.9.0", - "@types/yargs": "13.0.10", - "chalk": "2.4.2", + "@jest/console": "26.6.1", + "@jest/environment": "26.6.1", + "@jest/fake-timers": "26.6.1", + "@jest/globals": "26.6.1", + "@jest/source-map": "26.5.0", + "@jest/test-result": "26.6.1", + "@jest/transform": "26.6.1", + "@jest/types": "26.6.1", + "@types/yargs": "15.0.9", + "chalk": "4.1.0", + "cjs-module-lexer": "0.4.3", + "collect-v8-coverage": "1.0.1", "exit": "0.1.2", "glob": "7.1.6", "graceful-fs": "4.2.4", - "jest-config": "24.9.0", - "jest-haste-map": "24.9.0", - "jest-message-util": "24.9.0", - "jest-mock": "24.9.0", - "jest-regex-util": "24.9.0", - "jest-resolve": "24.9.0", - "jest-snapshot": "24.9.0", - "jest-util": "24.9.0", - "jest-validate": "24.9.0", - "realpath-native": "1.1.0", - "slash": "2.0.0", - "strip-bom": "3.0.0", - "yargs": "13.3.2" + "jest-config": "26.6.1", + "jest-haste-map": "26.6.1", + "jest-message-util": "26.6.1", + "jest-mock": "26.6.1", + "jest-regex-util": "26.0.0", + "jest-resolve": "26.6.1", + "jest-snapshot": "26.6.1", + "jest-util": "26.6.1", + "jest-validate": "26.6.1", + "slash": "3.0.0", + "strip-bom": "4.0.0", + "yargs": "15.4.1" }, "dependencies": { - "strip-bom": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - } - } - }, - "jest-serializer": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", - "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==" - }, - "jest-snapshot": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", - "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", - "requires": { - "@babel/types": "7.11.5", - "@jest/types": "24.9.0", - "chalk": "2.4.2", - "expect": "24.9.0", - "jest-diff": "24.9.0", - "jest-get-type": "24.9.0", - "jest-matcher-utils": "24.9.0", - "jest-message-util": "24.9.0", - "jest-resolve": "24.9.0", - "mkdirp": "0.5.5", - "natural-compare": "1.4.0", - "pretty-format": "24.9.0", - "semver": "6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "jest-util": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", - "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", - "requires": { - "@jest/console": "24.9.0", - "@jest/fake-timers": "24.9.0", - "@jest/source-map": "24.9.0", - "@jest/test-result": "24.9.0", - "@jest/types": "24.9.0", - "callsites": "3.1.0", - "chalk": "2.4.2", - "graceful-fs": "4.2.4", - "is-ci": "2.0.0", - "mkdirp": "0.5.5", - "slash": "2.0.0", - "source-map": "0.6.1" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - } - } - }, - "jest-validate": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", - "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", - "requires": { - "@jest/types": "24.9.0", - "camelcase": "5.3.1", - "chalk": "2.4.2", - "jest-get-type": "24.9.0", - "leven": "3.1.0", - "pretty-format": "24.9.0" - }, - "dependencies": { + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "requires": { + "string-width": "4.2.0", + "strip-ansi": "6.0.0", + "wrap-ansi": "6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "jest-resolve": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.1.tgz", + "integrity": "sha512-hiHfQH6rrcpAmw9xCQ0vD66SDuU+7ZulOuKwc4jpbmFFsz0bQG/Ib92K+9/489u5rVw0btr/ZhiHqBpmkbCvuQ==", + "requires": { + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "jest-pnp-resolver": "1.2.2", + "jest-util": "26.6.1", + "read-pkg-up": "7.0.1", + "resolve": "1.18.1", + "slash": "3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "7.10.4", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.1.0", + "type-fest": "0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "8.0.0", + "is-fullwidth-code-point": "3.0.0", + "strip-ansi": "6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "5.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "4.3.0", + "string-width": "4.2.0", + "strip-ansi": "6.0.0" + } + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "requires": { + "cliui": "6.0.0", + "decamelize": "1.2.0", + "find-up": "4.1.0", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "4.2.0", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "18.1.3" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "requires": { + "camelcase": "5.3.1", + "decamelize": "1.2.0" + } + } + } + }, + "jest-serializer": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.5.0.tgz", + "integrity": "sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA==", + "requires": { + "@types/node": "14.14.3", + "graceful-fs": "4.2.4" + } + }, + "jest-snapshot": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.1.tgz", + "integrity": "sha512-JA7bZp7HRTIJYAi85pJ/OZ2eur2dqmwIToA5/6d7Mn90isGEfeF9FvuhDLLEczgKP1ihreBzrJ6Vr7zteP5JNA==", + "requires": { + "@babel/types": "7.12.1", + "@jest/types": "26.6.1", + "@types/babel__traverse": "7.0.15", + "@types/prettier": "2.1.5", + "chalk": "4.1.0", + "expect": "26.6.1", + "graceful-fs": "4.2.4", + "jest-diff": "26.6.1", + "jest-get-type": "26.3.0", + "jest-haste-map": "26.6.1", + "jest-matcher-utils": "26.6.1", + "jest-message-util": "26.6.1", + "jest-resolve": "26.6.1", + "natural-compare": "1.4.0", + "pretty-format": "26.6.1", + "semver": "7.3.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "diff-sequences": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz", + "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "5.0.0", + "path-exists": "4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-diff": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.1.tgz", + "integrity": "sha512-BBNy/zin2m4kG5In126O8chOBxLLS/XMTuuM2+YhgyHk87ewPzKTuTJcqj3lOWOi03NNgrl+DkMeV/exdvG9gg==", + "requires": { + "chalk": "4.1.0", + "diff-sequences": "26.5.0", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "jest-matcher-utils": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.1.tgz", + "integrity": "sha512-9iu3zrsYlUnl8pByhREF9rr5eYoiEb1F7ymNKg6lJr/0qD37LWS5FSW/JcoDl8UdMX2+zAzabDs7sTO+QFKjCg==", + "requires": { + "chalk": "4.1.0", + "jest-diff": "26.6.1", + "jest-get-type": "26.3.0", + "pretty-format": "26.6.1" + } + }, + "jest-resolve": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.1.tgz", + "integrity": "sha512-hiHfQH6rrcpAmw9xCQ0vD66SDuU+7ZulOuKwc4jpbmFFsz0bQG/Ib92K+9/489u5rVw0btr/ZhiHqBpmkbCvuQ==", + "requires": { + "@jest/types": "26.6.1", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "jest-pnp-resolver": "1.2.2", + "jest-util": "26.6.1", + "read-pkg-up": "7.0.1", + "resolve": "1.18.1", + "slash": "3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "2.3.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "7.10.4", + "error-ex": "1.3.2", + "json-parse-even-better-errors": "2.3.1", + "lines-and-columns": "1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.1.0", + "type-fest": "0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } + } + }, + "jest-util": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.1.tgz", + "integrity": "sha512-xCLZUqVoqhquyPLuDXmH7ogceGctbW8SMyQVjD9o+1+NPWI7t0vO08udcFLVPLgKWcvc+zotaUv/RuaR6l8HIA==", + "requires": { + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "chalk": "4.1.0", + "graceful-fs": "4.2.4", + "is-ci": "2.0.0", + "micromatch": "4.0.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } + } + }, + "jest-validate": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.1.tgz", + "integrity": "sha512-BEFpGbylKocnNPZULcnk+TGaz1oFZQH/wcaXlaXABbu0zBwkOGczuWgdLucUouuQqn7VadHZZeTvo8VSFDLMOA==", + "requires": { + "@jest/types": "26.6.1", + "camelcase": "6.1.0", + "chalk": "4.1.0", + "jest-get-type": "26.3.0", + "leven": "3.1.0", + "pretty-format": "26.6.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "camelcase": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==" + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, + "pretty-format": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.1.tgz", + "integrity": "sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA==", + "requires": { + "@jest/types": "26.6.1", + "ansi-regex": "5.0.0", + "ansi-styles": "4.3.0", + "react-is": "17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } } } }, "jest-watch-typeahead": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.4.2.tgz", - "integrity": "sha512-f7VpLebTdaXs81rg/oj4Vg/ObZy2QtGzAmGLNsqUS5G5KtSN68tFcIsbvNODfNyQxU78g7D8x77o3bgfBTR+2Q==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz", + "integrity": "sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg==", "requires": { "ansi-escapes": "4.3.1", - "chalk": "2.4.2", - "jest-regex-util": "24.9.0", - "jest-watcher": "24.9.0", + "chalk": "4.1.0", + "jest-regex-util": "26.0.0", + "jest-watcher": "26.6.1", "slash": "3.0.0", - "string-length": "3.1.0", - "strip-ansi": "5.2.0" + "string-length": "4.0.1", + "strip-ansi": "6.0.0" }, "dependencies": { - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, - "string-length": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", - "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "astral-regex": "1.0.0", - "strip-ansi": "5.2.0" + "color-convert": "2.0.1" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { - "ansi-regex": "4.1.0" + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" } } } }, "jest-watcher": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", - "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.1.tgz", + "integrity": "sha512-0LBIPPncNi9CaLKK15bnxyd2E8OMl4kJg0PTiNOI+MXztXw1zVdtX/x9Pr6pXaQYps+eS/ts43O4+HByZ7yJSw==", "requires": { - "@jest/test-result": "24.9.0", - "@jest/types": "24.9.0", - "@types/yargs": "13.0.10", - "ansi-escapes": "3.2.0", - "chalk": "2.4.2", - "jest-util": "24.9.0", - "string-length": "2.0.0" + "@jest/test-result": "26.6.1", + "@jest/types": "26.6.1", + "@types/node": "14.14.3", + "ansi-escapes": "4.3.1", + "chalk": "4.1.0", + "jest-util": "26.6.1", + "string-length": "4.0.1" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "@jest/types": { + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.1.tgz", + "integrity": "sha512-ywHavIKNpAVrStiRY5wiyehvcktpijpItvGiK72RAn5ctqmzvPk8OvKnvHeBqa1XdQr959CTWAJMqxI8BTibyg==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "@types/istanbul-reports": "3.0.0", + "@types/node": "14.14.3", + "@types/yargs": "15.0.9", + "chalk": "4.1.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", + "integrity": "sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==", + "requires": { + "@types/yargs-parser": "15.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "4.3.0", + "supports-color": "7.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } } } }, "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "version": "26.6.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.1.tgz", + "integrity": "sha512-R5IE3qSGz+QynJx8y+ICEkdI2OJ3RJjRQVEyCcFAd3yVhQSEtquziPO29Mlzgn07LOVE8u8jhJ1FqcwegiXWOw==", "requires": { + "@types/node": "14.14.3", "merge-stream": "2.0.0", - "supports-color": "6.1.0" + "supports-color": "7.2.0" }, "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "requires": { - "has-flag": "3.0.0" + "has-flag": "4.0.0" } } } @@ -7988,42 +12194,47 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", "requires": { - "abab": "2.0.4", - "acorn": "5.7.4", - "acorn-globals": "4.3.4", - "array-equal": "1.0.0", - "cssom": "0.3.8", - "cssstyle": "1.4.0", - "data-urls": "1.1.0", - "domexception": "1.0.1", + "abab": "2.0.5", + "acorn": "7.4.1", + "acorn-globals": "6.0.0", + "cssom": "0.4.4", + "cssstyle": "2.3.0", + "data-urls": "2.0.0", + "decimal.js": "10.2.1", + "domexception": "2.0.1", "escodegen": "1.14.3", - "html-encoding-sniffer": "1.0.2", - "left-pad": "1.3.0", + "html-encoding-sniffer": "2.0.1", + "is-potential-custom-element-name": "1.0.0", "nwsapi": "2.2.0", - "parse5": "4.0.0", - "pn": "1.1.0", + "parse5": "5.1.1", "request": "2.88.2", "request-promise-native": "1.0.9", - "sax": "1.2.4", + "saxes": "5.0.1", "symbol-tree": "3.2.4", - "tough-cookie": "2.5.0", + "tough-cookie": "3.0.1", "w3c-hr-time": "1.0.2", - "webidl-conversions": "4.0.2", + "w3c-xmlserializer": "2.0.0", + "webidl-conversions": "6.1.0", "whatwg-encoding": "1.0.5", "whatwg-mimetype": "2.3.0", - "whatwg-url": "6.5.0", - "ws": "5.2.2", + "whatwg-url": "8.4.0", + "ws": "7.3.1", "xml-name-validator": "3.0.0" }, "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "2.1.0", + "psl": "1.8.0", + "punycode": "2.1.1" + } } } }, @@ -8052,14 +12263,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "0.0.0" - } - }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -8084,18 +12287,14 @@ } }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "requires": { - "graceful-fs": "4.2.4" + "graceful-fs": "4.2.4", + "universalify": "1.0.0" } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -8108,12 +12307,12 @@ } }, "jsx-ast-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", - "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz", + "integrity": "sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==", "requires": { "array-includes": "3.1.1", - "object.assign": "4.1.0" + "object.assign": "4.1.1" } }, "killable": { @@ -8134,6 +12333,19 @@ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" }, + "language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", + "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==" + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "requires": { + "language-subtag-registry": "0.3.20" + } + }, "last-call-webpack-plugin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", @@ -8148,39 +12360,37 @@ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "requires": { - "invert-kv": "2.0.0" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "requires": { - "leven": "3.1.0" + "prelude-ls": "1.2.1", + "type-check": "0.4.0" } }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "line-column": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz", + "integrity": "sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI=", "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "isarray": "1.0.0", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } } }, "lines-and-columns": { @@ -8200,58 +12410,19 @@ "strip-bom": "2.0.0" } }, - "loader-fs-cache": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz", - "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==", - "requires": { - "find-cache-dir": "0.1.1", - "mkdirp": "0.5.5" - }, - "dependencies": { - "find-cache-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", - "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", - "requires": { - "commondir": "1.0.1", - "mkdirp": "0.5.5", - "pkg-dir": "1.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "requires": { - "find-up": "1.1.2" - } - } - } - }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", "requires": { "big.js": "5.2.2", "emojis-list": "3.0.0", - "json5": "1.0.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "1.2.5" - } - } + "json5": "2.1.3" } }, "locate-path": { @@ -8345,7 +12516,7 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", "requires": { - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "lru-cache": { @@ -8357,6 +12528,14 @@ "yallist": "2.1.2" } }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "requires": { + "sourcemap-codec": "1.4.8" + } + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -8370,11 +12549,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -8386,19 +12560,6 @@ "tmpl": "1.0.4" } }, - "mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==" - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "requires": { - "p-defer": "1.0.0" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -8437,16 +12598,6 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "requires": { - "map-age-cleaner": "0.1.3", - "mimic-fn": "2.1.0", - "p-is-promise": "2.1.0" - } - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -8536,30 +12687,12 @@ "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.3", - "nanomatch": "1.2.13", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - } + "braces": "3.0.2", + "picomatch": "2.2.2" } }, "miller-rabin": { @@ -8579,9 +12712,9 @@ } }, "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { "version": "1.44.0", @@ -8607,9 +12740,9 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, "mini-css-extract-plugin": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", - "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", + "integrity": "sha512-n9BA8LonkOkW1/zn+IbLPQmovsL0wMb9yx75fMJQZf2X1Zoec9yTZtyMePcyu19wPkmFbzZZA6fLTotpFhQsOA==", "requires": { "loader-utils": "1.4.0", "normalize-url": "1.9.1", @@ -8617,12 +12750,30 @@ "webpack-sources": "1.4.3" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { - "ajv": "6.12.4", + "ajv": "6.12.6", "ajv-errors": "1.0.1", "ajv-keywords": "3.5.2" } @@ -8691,6 +12842,22 @@ "minipass": "3.1.3" } }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "3.1.3", + "yallist": "4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -8789,9 +12956,14 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "nanoid": { + "version": "3.1.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz", + "integrity": "sha512-+AK8MN0WHji40lj8AEuwLOvLSbWYApQpre/aFJZD71r43wVRLrOYS4FmJOPQYon1TqB462RzrrxlfA74XRES8w==" }, "nanomatch": { "version": "1.2.13", @@ -8818,6 +12990,14 @@ } } }, + "native-url": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/native-url/-/native-url-0.2.6.tgz", + "integrity": "sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA==", + "requires": { + "querystring": "0.2.0" + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8849,13 +13029,13 @@ "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", "requires": { "lower-case": "2.0.1", - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, "node-gyp": { "version": "3.8.0", @@ -8893,7 +13073,7 @@ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", "requires": { - "assert": "1.4.1", + "assert": "1.5.0", "browserify-zlib": "0.2.0", "buffer": "4.9.2", "console-browserify": "1.2.0", @@ -8918,23 +13098,10 @@ "vm-browserify": "1.1.2" }, "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "requires": { - "inherits": "2.0.3" - } } } }, @@ -8944,28 +13111,46 @@ "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" }, "node-notifier": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", - "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz", + "integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==", + "optional": true, "requires": { "growly": "1.3.0", - "is-wsl": "1.1.0", - "semver": "5.7.1", + "is-wsl": "2.2.0", + "semver": "7.3.2", "shellwords": "0.1.1", - "which": "1.3.1" + "uuid": "8.3.1", + "which": "2.0.2" }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "optional": true + }, + "uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", + "optional": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "requires": { + "isexe": "2.0.0" + } } } }, "node-releases": { - "version": "1.1.60", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", - "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==" + "version": "1.1.64", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.64.tgz", + "integrity": "sha512-Iec8O9166/x2HRMJyLLLWkd0sFFLrFNy+Xf+JQfSQsdBJzPcHpNl3JQ9gD4j+aJxmCa25jNsIbM4bmACtSbkSg==" }, "node-sass": { "version": "4.14.1", @@ -8982,7 +13167,7 @@ "lodash": "4.17.20", "meow": "3.7.0", "mkdirp": "0.5.5", - "nan": "2.14.1", + "nan": "2.14.2", "node-gyp": "3.8.0", "npmlog": "4.1.2", "request": "2.88.2", @@ -9029,25 +13214,15 @@ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "requires": { "hosted-git-info": "2.8.8", - "resolve": "1.17.0", + "resolve": "1.18.1", "semver": "5.7.1", "validate-npm-package-license": "3.0.4" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "1.1.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -9137,23 +13312,18 @@ } } }, - "object-hash": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", - "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==" - }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" }, "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz", + "integrity": "sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6" + "es-abstract": "1.18.0-next.1" } }, "object-keys": { @@ -9161,11 +13331,6 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, - "object-path": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz", - "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=" - }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -9175,12 +13340,12 @@ } }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", "requires": { "define-properties": "1.1.3", - "function-bind": "1.1.1", + "es-abstract": "1.18.0-next.1", "has-symbols": "1.0.1", "object-keys": "1.1.1" } @@ -9191,8 +13356,28 @@ "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6", + "es-abstract": "1.17.7", "has": "1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "object.fromentries": { @@ -9201,9 +13386,29 @@ "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6", + "es-abstract": "1.17.7", "function-bind": "1.1.1", "has": "1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "object.getownpropertydescriptors": { @@ -9212,7 +13417,27 @@ "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6" + "es-abstract": "1.17.7" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "object.pick": { @@ -9229,9 +13454,29 @@ "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6", + "es-abstract": "1.17.7", "function-bind": "1.1.1", "has": "1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "obuf": { @@ -9269,22 +13514,12 @@ } }, "open": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/open/-/open-7.2.1.tgz", - "integrity": "sha512-xbYCJib4spUdmcs0g/2mK1nKo/jO2T7INClWd/beL7PFkXRWgr8B23ssDHX/USPn2M2IjDR5UdpYs6I67SnTSA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", "requires": { "is-docker": "2.1.1", "is-wsl": "2.2.0" - }, - "dependencies": { - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "2.1.1" - } - } } }, "opn": { @@ -9293,27 +13528,34 @@ "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", "requires": { "is-wsl": "1.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + } } }, "optimize-css-assets-webpack-plugin": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz", - "integrity": "sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", + "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", "requires": { "cssnano": "4.1.10", "last-call-webpack-plugin": "3.0.0" } }, "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "requires": { "deep-is": "0.1.3", "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", + "levn": "0.4.1", + "prelude-ls": "1.2.1", + "type-check": "0.4.0", "word-wrap": "1.2.3" } }, @@ -9335,16 +13577,6 @@ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "requires": { - "execa": "1.0.0", - "lcid": "2.0.0", - "mem": "4.3.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -9359,29 +13591,16 @@ "os-tmpdir": "1.0.2" } }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" - }, "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", - "requires": { - "p-reduce": "1.0.0" - } + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", + "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==" }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -9399,18 +13618,13 @@ } }, "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "requires": { "aggregate-error": "3.1.0" } }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" - }, "p-retry": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", @@ -9445,7 +13659,7 @@ "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", "requires": { "dot-case": "3.0.3", - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "parent-module": { @@ -9454,13 +13668,6 @@ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "requires": { "callsites": "3.1.0" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - } } }, "parse-asn1": { @@ -9490,9 +13697,9 @@ "dev": true }, "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" }, "parseurl": { "version": "1.3.3", @@ -9505,7 +13712,7 @@ "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", "requires": { "no-case": "3.0.3", - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "pascalcase": { @@ -9650,17 +13857,12 @@ } } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" - }, "pnp-webpack-plugin": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", "requires": { - "ts-pnp": "1.1.6" + "ts-pnp": "1.2.0" } }, "popper.js": { @@ -9694,9 +13896,9 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { - "version": "7.0.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", - "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", "requires": { "chalk": "2.4.2", "source-map": "0.6.1", @@ -9718,8 +13920,8 @@ "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", "requires": { - "postcss": "7.0.32", - "postcss-selector-parser": "6.0.2" + "postcss": "7.0.35", + "postcss-selector-parser": "6.0.4" } }, "postcss-browser-comments": { @@ -9727,16 +13929,16 @@ "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz", "integrity": "sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-calc": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.4.tgz", - "integrity": "sha512-0I79VRAd1UTkaHzY9w83P39YGO/M3bG7/tNLrHGEunBolfoGM0hSjrGvjoeaj0JE/zIw5GsI2KZ0UwDJqv5hjw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", "requires": { - "postcss": "7.0.32", - "postcss-selector-parser": "6.0.2", + "postcss": "7.0.35", + "postcss-selector-parser": "6.0.4", "postcss-value-parser": "4.1.0" } }, @@ -9745,7 +13947,7 @@ "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9755,7 +13957,7 @@ "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", "requires": { "@csstools/convert-colors": "1.4.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9764,7 +13966,7 @@ "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9774,7 +13976,7 @@ "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", "requires": { "@csstools/convert-colors": "1.4.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9783,7 +13985,7 @@ "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9792,10 +13994,10 @@ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", "requires": { - "browserslist": "4.14.1", - "color": "3.1.2", + "browserslist": "4.14.5", + "color": "3.1.3", "has": "1.0.3", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -9811,7 +14013,7 @@ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -9827,7 +14029,7 @@ "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-custom-properties": { @@ -9835,7 +14037,7 @@ "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9844,7 +14046,7 @@ "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-selector-parser": "5.0.0" }, "dependencies": { @@ -9870,7 +14072,7 @@ "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-selector-parser": "5.0.0" }, "dependencies": { @@ -9896,7 +14098,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-discard-duplicates": { @@ -9904,7 +14106,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-discard-empty": { @@ -9912,7 +14114,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-discard-overridden": { @@ -9920,7 +14122,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-double-position-gradients": { @@ -9928,7 +14130,7 @@ "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9937,16 +14139,16 @@ "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, "postcss-flexbugs-fixes": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz", - "integrity": "sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz", + "integrity": "sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-focus-visible": { @@ -9954,7 +14156,7 @@ "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-focus-within": { @@ -9962,7 +14164,7 @@ "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-font-variant": { @@ -9970,7 +14172,7 @@ "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-gap-properties": { @@ -9978,7 +14180,7 @@ "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-image-set-function": { @@ -9986,7 +14188,7 @@ "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -9996,7 +14198,7 @@ "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", "requires": { "lodash.template": "4.5.0", - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-lab-function": { @@ -10005,17 +14207,53 @@ "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", "requires": { "@csstools/convert-colors": "1.4.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", "requires": { "cosmiconfig": "5.2.1", "import-cwd": "2.1.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "2.0.0", + "is-directory": "0.3.1", + "js-yaml": "3.14.0", + "parse-json": "4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "2.0.0", + "resolve-from": "3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } } }, "postcss-loader": { @@ -10024,17 +14262,35 @@ "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "requires": { "loader-utils": "1.4.0", - "postcss": "7.0.32", - "postcss-load-config": "2.1.0", + "postcss": "7.0.35", + "postcss-load-config": "2.1.2", "schema-utils": "1.0.0" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { - "ajv": "6.12.4", + "ajv": "6.12.6", "ajv-errors": "1.0.1", "ajv-keywords": "3.5.2" } @@ -10046,7 +14302,7 @@ "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-media-minmax": { @@ -10054,7 +14310,7 @@ "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-merge-longhand": { @@ -10063,7 +14319,7 @@ "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", "requires": { "css-color-names": "0.0.4", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1", "stylehacks": "4.0.3" }, @@ -10080,10 +14336,10 @@ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", "requires": { - "browserslist": "4.14.1", + "browserslist": "4.14.5", "caniuse-api": "3.0.0", "cssnano-util-same-parent": "4.0.1", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-selector-parser": "3.1.2", "vendors": "1.0.4" }, @@ -10105,7 +14361,7 @@ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10123,7 +14379,7 @@ "requires": { "cssnano-util-get-arguments": "4.0.0", "is-color-stop": "1.1.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10140,9 +14396,9 @@ "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", "requires": { "alphanum-sort": "1.0.2", - "browserslist": "4.14.1", + "browserslist": "4.14.5", "cssnano-util-get-arguments": "4.0.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1", "uniqs": "2.0.0" }, @@ -10161,7 +14417,7 @@ "requires": { "alphanum-sort": "1.0.2", "has": "1.0.3", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-selector-parser": "3.1.2" }, "dependencies": { @@ -10182,7 +14438,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-modules-local-by-default": { @@ -10191,8 +14447,8 @@ "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", "requires": { "icss-utils": "4.1.1", - "postcss": "7.0.32", - "postcss-selector-parser": "6.0.2", + "postcss": "7.0.35", + "postcss-selector-parser": "6.0.4", "postcss-value-parser": "4.1.0" } }, @@ -10201,8 +14457,8 @@ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", "requires": { - "postcss": "7.0.32", - "postcss-selector-parser": "6.0.2" + "postcss": "7.0.35", + "postcss-selector-parser": "6.0.4" } }, "postcss-modules-values": { @@ -10211,7 +14467,7 @@ "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", "requires": { "icss-utils": "4.1.1", - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-nesting": { @@ -10219,7 +14475,7 @@ "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-normalize": { @@ -10228,8 +14484,8 @@ "integrity": "sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ==", "requires": { "@csstools/normalize.css": "10.1.0", - "browserslist": "4.14.1", - "postcss": "7.0.32", + "browserslist": "4.14.5", + "postcss": "7.0.35", "postcss-browser-comments": "3.0.0", "sanitize.css": "10.0.0" } @@ -10239,7 +14495,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-normalize-display-values": { @@ -10248,7 +14504,7 @@ "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", "requires": { "cssnano-util-get-match": "4.0.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10266,7 +14522,7 @@ "requires": { "cssnano-util-get-arguments": "4.0.0", "has": "1.0.3", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10284,7 +14540,7 @@ "requires": { "cssnano-util-get-arguments": "4.0.0", "cssnano-util-get-match": "4.0.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10301,7 +14557,7 @@ "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", "requires": { "has": "1.0.3", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10318,7 +14574,7 @@ "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", "requires": { "cssnano-util-get-match": "4.0.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10334,8 +14590,8 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", "requires": { - "browserslist": "4.14.1", - "postcss": "7.0.32", + "browserslist": "4.14.5", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10353,7 +14609,7 @@ "requires": { "is-absolute-url": "2.1.0", "normalize-url": "3.3.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10374,7 +14630,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10391,7 +14647,7 @@ "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", "requires": { "cssnano-util-get-arguments": "4.0.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10407,7 +14663,7 @@ "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-page-break": { @@ -10415,7 +14671,7 @@ "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-place": { @@ -10423,7 +14679,7 @@ "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-values-parser": "2.0.1" } }, @@ -10433,13 +14689,13 @@ "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", "requires": { "autoprefixer": "9.8.6", - "browserslist": "4.14.1", - "caniuse-lite": "1.0.30001124", + "browserslist": "4.14.5", + "caniuse-lite": "1.0.30001151", "css-blank-pseudo": "0.1.4", "css-has-pseudo": "0.10.0", "css-prefers-color-scheme": "3.1.1", "cssdb": "4.4.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-attribute-case-insensitive": "4.0.2", "postcss-color-functional-notation": "2.0.1", "postcss-color-gray": "5.0.0", @@ -10476,7 +14732,7 @@ "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", "requires": { - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-selector-parser": "5.0.0" }, "dependencies": { @@ -10502,10 +14758,10 @@ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", "requires": { - "browserslist": "4.14.1", + "browserslist": "4.14.5", "caniuse-api": "3.0.0", "has": "1.0.3", - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-reduce-transforms": { @@ -10515,7 +14771,7 @@ "requires": { "cssnano-util-get-match": "4.0.0", "has": "1.0.3", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1" }, "dependencies": { @@ -10531,15 +14787,28 @@ "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", "requires": { - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-safe-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz", - "integrity": "sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-5.0.2.tgz", + "integrity": "sha512-jDUfCPJbKOABhwpUKcqCVbbXiloe/QXMcbJ6Iipf3sDIihEzTqRCeMBfRaOHxhBuTYqtASrI1KJWxzztZU4qUQ==", "requires": { - "postcss": "7.0.32" + "postcss": "8.1.4" + }, + "dependencies": { + "postcss": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.1.4.tgz", + "integrity": "sha512-LfqcwgMq9LOd8pX7K2+r2HPitlIGC5p6PoZhVELlqhh2YGDVcXKpkCseqan73Hrdik6nBd2OvoDPUaP/oMj9hQ==", + "requires": { + "colorette": "1.2.1", + "line-column": "1.0.2", + "nanoid": "3.1.16", + "source-map": "0.6.1" + } + } } }, "postcss-selector-matches": { @@ -10548,7 +14817,7 @@ "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", "requires": { "balanced-match": "1.0.0", - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-selector-not": { @@ -10557,17 +14826,18 @@ "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", "requires": { "balanced-match": "1.0.0", - "postcss": "7.0.32" + "postcss": "7.0.35" } }, "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", "requires": { "cssesc": "3.0.0", "indexes-of": "1.0.1", - "uniq": "1.0.1" + "uniq": "1.0.1", + "util-deprecate": "1.0.2" } }, "postcss-svgo": { @@ -10576,7 +14846,7 @@ "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", "requires": { "is-svg": "3.0.0", - "postcss": "7.0.32", + "postcss": "7.0.35", "postcss-value-parser": "3.3.1", "svgo": "1.3.2" }, @@ -10594,7 +14864,7 @@ "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", "requires": { "alphanum-sort": "1.0.2", - "postcss": "7.0.32", + "postcss": "7.0.35", "uniqs": "2.0.0" } }, @@ -10614,9 +14884,9 @@ } }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, "prepend-http": { "version": "1.0.4", @@ -10629,12 +14899,12 @@ "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==" }, "pretty-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", - "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", + "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", "requires": { - "renderkid": "2.0.3", - "utila": "0.4.0" + "lodash": "4.17.20", + "renderkid": "2.0.4" } }, "pretty-format": { @@ -10677,9 +14947,9 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "prompts": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", - "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", + "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", "requires": { "kleur": "3.0.3", "sisteransi": "1.0.5" @@ -10857,9 +15127,9 @@ } }, "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "requires": { "loose-envify": "1.4.0", "object-assign": "4.1.1", @@ -10867,9 +15137,9 @@ } }, "react-app-polyfill": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz", - "integrity": "sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", + "integrity": "sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA==", "requires": { "core-js": "3.6.5", "object-assign": "4.1.1", @@ -10886,96 +15156,65 @@ "dev": true, "requires": { "semver": "5.7.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } } }, "react-dev-utils": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", - "integrity": "sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.0.tgz", + "integrity": "sha512-uIZTUZXB5tbiM/0auUkLVjWhZGM7DSI304iGunyhA9m985iIDVXd9I4z6MkNa9jeLzeUJbU9A7TUNrcbXAahxw==", "requires": { - "@babel/code-frame": "7.8.3", + "@babel/code-frame": "7.10.4", "address": "1.1.2", - "browserslist": "4.10.0", + "browserslist": "4.14.2", "chalk": "2.4.2", - "cross-spawn": "7.0.1", + "cross-spawn": "7.0.3", "detect-port-alt": "1.1.6", "escape-string-regexp": "2.0.0", - "filesize": "6.0.1", + "filesize": "6.1.0", "find-up": "4.1.0", - "fork-ts-checker-webpack-plugin": "3.1.1", + "fork-ts-checker-webpack-plugin": "4.1.6", "global-modules": "2.0.0", - "globby": "8.0.2", + "globby": "11.0.1", "gzip-size": "5.1.1", - "immer": "1.10.0", - "inquirer": "7.0.4", + "immer": "7.0.9", + "inquirer": "7.3.3", "is-root": "2.1.0", - "loader-utils": "1.2.3", - "open": "7.2.1", + "loader-utils": "2.0.0", + "open": "7.3.0", "pkg-up": "3.1.0", - "react-error-overlay": "6.0.7", + "react-error-overlay": "6.0.8", "recursive-readdir": "2.2.2", "shell-quote": "1.7.2", "strip-ansi": "6.0.0", "text-table": "0.2.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "7.10.4" - } - }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "browserslist": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.10.0.tgz", - "integrity": "sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", + "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", "requires": { - "caniuse-lite": "1.0.30001124", - "electron-to-chromium": "1.3.564", - "node-releases": "1.1.60", - "pkg-up": "3.1.0" + "caniuse-lite": "1.0.30001151", + "electron-to-chromium": "1.3.583", + "escalade": "3.1.1", + "node-releases": "1.1.64" } }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" - }, "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "requires": { "path-key": "3.1.1", "shebang-command": "2.0.0", "which": "2.0.2" } }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" - }, "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -10990,64 +15229,6 @@ "path-exists": "4.0.0" } }, - "inquirer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", - "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", - "requires": { - "ansi-escapes": "4.3.1", - "chalk": "2.4.2", - "cli-cursor": "3.1.0", - "cli-width": "2.2.1", - "external-editor": "3.1.0", - "figures": "3.2.0", - "lodash": "4.17.20", - "mute-stream": "0.0.8", - "run-async": "2.4.1", - "rxjs": "6.6.3", - "string-width": "4.2.0", - "strip-ansi": "5.2.0", - "through": "2.3.8" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "4.1.0" - } - } - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "1.2.5" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "requires": { - "big.js": "5.2.2", - "emojis-list": "2.1.0", - "json5": "1.0.1" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -11087,16 +15268,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "8.0.0", - "is-fullwidth-code-point": "3.0.0", - "strip-ansi": "6.0.0" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -11116,9 +15287,9 @@ } }, "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", "requires": { "loose-envify": "1.4.0", "object-assign": "4.1.1", @@ -11127,9 +15298,9 @@ } }, "react-error-overlay": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz", - "integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==" + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", + "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==" }, "react-is": { "version": "16.13.1", @@ -11146,7 +15317,7 @@ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz", "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==", "requires": { - "@babel/runtime": "7.11.2", + "@babel/runtime": "7.12.1", "create-react-context": "0.3.0", "deep-equal": "1.1.1", "popper.js": "1.16.1", @@ -11155,83 +15326,85 @@ "warning": "4.0.3" } }, + "react-refresh": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", + "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==" + }, "react-scripts": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.1.tgz", - "integrity": "sha512-JpTdi/0Sfd31mZA6Ukx+lq5j1JoKItX7qqEK4OiACjVQletM1P38g49d9/D0yTxp9FrSF+xpJFStkGgKEIRjlQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.0.tgz", + "integrity": "sha512-icJ/ctwV5XwITUOupBP9TUVGdWOqqZ0H08tbJ1kVC5VpNWYzEZ3e/x8axhV15ZXRsixLo27snwQE7B6Zd9J2Tg==", "requires": { - "@babel/core": "7.9.0", - "@svgr/webpack": "4.3.3", - "@typescript-eslint/eslint-plugin": "2.34.0", - "@typescript-eslint/parser": "2.34.0", + "@babel/core": "7.12.3", + "@pmmmwh/react-refresh-webpack-plugin": "0.4.2", + "@svgr/webpack": "5.4.0", + "@typescript-eslint/eslint-plugin": "4.6.0", + "@typescript-eslint/parser": "4.6.0", "babel-eslint": "10.1.0", - "babel-jest": "24.9.0", + "babel-jest": "26.6.1", "babel-loader": "8.1.0", - "babel-plugin-named-asset-import": "0.3.6", - "babel-preset-react-app": "9.1.2", - "camelcase": "5.3.1", + "babel-plugin-named-asset-import": "0.3.7", + "babel-preset-react-app": "10.0.0", + "bfj": "7.0.2", + "camelcase": "6.1.0", "case-sensitive-paths-webpack-plugin": "2.3.0", - "css-loader": "3.4.2", + "css-loader": "4.3.0", "dotenv": "8.2.0", "dotenv-expand": "5.1.0", - "eslint": "6.8.0", - "eslint-config-react-app": "5.2.1", - "eslint-loader": "3.0.3", - "eslint-plugin-flowtype": "4.6.0", - "eslint-plugin-import": "2.20.1", - "eslint-plugin-jsx-a11y": "6.2.3", - "eslint-plugin-react": "7.19.0", - "eslint-plugin-react-hooks": "1.7.0", - "file-loader": "4.3.0", - "fs-extra": "8.1.0", - "fsevents": "2.1.2", - "html-webpack-plugin": "4.0.0-beta.11", + "eslint": "7.12.0", + "eslint-config-react-app": "6.0.0", + "eslint-plugin-flowtype": "5.2.0", + "eslint-plugin-import": "2.22.1", + "eslint-plugin-jest": "24.1.0", + "eslint-plugin-jsx-a11y": "6.4.1", + "eslint-plugin-react": "7.21.5", + "eslint-plugin-react-hooks": "4.2.0", + "eslint-plugin-testing-library": "3.9.2", + "eslint-webpack-plugin": "2.1.0", + "file-loader": "6.1.1", + "fs-extra": "9.0.1", + "fsevents": "2.1.3", + "html-webpack-plugin": "4.5.0", "identity-obj-proxy": "3.0.0", - "jest": "24.9.0", - "jest-environment-jsdom-fourteen": "1.0.1", - "jest-resolve": "24.9.0", - "jest-watch-typeahead": "0.4.2", - "mini-css-extract-plugin": "0.9.0", - "optimize-css-assets-webpack-plugin": "5.0.3", + "jest": "26.6.0", + "jest-circus": "26.6.0", + "jest-resolve": "26.6.0", + "jest-watch-typeahead": "0.6.1", + "mini-css-extract-plugin": "0.11.3", + "optimize-css-assets-webpack-plugin": "5.0.4", "pnp-webpack-plugin": "1.6.4", - "postcss-flexbugs-fixes": "4.1.0", + "postcss-flexbugs-fixes": "4.2.1", "postcss-loader": "3.0.0", "postcss-normalize": "8.0.1", "postcss-preset-env": "6.7.0", - "postcss-safe-parser": "4.0.1", - "react-app-polyfill": "1.0.6", - "react-dev-utils": "10.2.1", - "resolve": "1.15.0", - "resolve-url-loader": "3.1.1", + "postcss-safe-parser": "5.0.2", + "react-app-polyfill": "2.0.0", + "react-dev-utils": "11.0.0", + "react-refresh": "0.8.3", + "resolve": "1.18.1", + "resolve-url-loader": "3.1.2", "sass-loader": "8.0.2", - "semver": "6.3.0", - "style-loader": "0.23.1", - "terser-webpack-plugin": "2.3.5", - "ts-pnp": "1.1.6", - "url-loader": "2.3.0", - "webpack": "4.42.0", - "webpack-dev-server": "3.10.3", + "semver": "7.3.2", + "style-loader": "1.3.0", + "terser-webpack-plugin": "4.2.3", + "ts-pnp": "1.2.0", + "url-loader": "4.1.1", + "webpack": "4.44.2", + "webpack-dev-server": "3.11.0", "webpack-manifest-plugin": "2.2.0", - "workbox-webpack-plugin": "4.3.1" + "workbox-webpack-plugin": "5.1.4" }, "dependencies": { "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "resolve": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", - "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==", - "requires": { - "path-parse": "1.0.6" - } + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==" }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" } } }, @@ -11247,11 +15420,11 @@ } }, "reactstrap": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.5.1.tgz", - "integrity": "sha512-igpdw8DiW48ZtwGOo2unwlsILFlF7deiqFUAqc3wrsX/0H0OkvmezJdkjJx2X9jaHfjGdPpm0vu5VN/kk7tv+A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.6.0.tgz", + "integrity": "sha512-03/UMbLPR6MhVStVUfCLuKh8xh4JOtNVkRxDB9/uHixN+cEQPOpSYa0K69YyK1/2YdZBs2qS6y0cQkK8NQKBHA==", "requires": { - "@babel/runtime": "7.11.2", + "@babel/runtime": "7.12.1", "classnames": "2.2.6", "prop-types": "15.7.2", "react-popper": "1.3.7", @@ -11292,21 +15465,14 @@ } }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "optional": true, "requires": { "picomatch": "2.2.2" } }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "requires": { - "util.promisify": "1.0.1" - } - }, "recursive-readdir": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", @@ -11347,7 +15513,7 @@ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "requires": { - "@babel/runtime": "7.11.2" + "@babel/runtime": "7.12.1" } }, "regex-not": { @@ -11360,9 +15526,9 @@ } }, "regex-parser": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.10.tgz", - "integrity": "sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA==" + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" }, "regexp.prototype.flags": { "version": "1.3.0", @@ -11370,7 +15536,27 @@ "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6" + "es-abstract": "1.17.7" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "regexpp": { @@ -11379,9 +15565,9 @@ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==" }, "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", "requires": { "regenerate": "1.4.1", "regenerate-unicode-properties": "8.2.0", @@ -11422,15 +15608,15 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "renderkid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", - "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", + "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", "requires": { "css-select": "1.2.0", "dom-converter": "0.2.0", "htmlparser2": "3.10.1", - "strip-ansi": "3.0.1", - "utila": "0.4.0" + "lodash": "4.17.20", + "strip-ansi": "3.0.1" }, "dependencies": { "css-select": { @@ -11539,19 +15725,27 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", "requires": { + "is-core-module": "2.0.0", "path-parse": "1.0.6" } }, "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "requires": { - "resolve-from": "3.0.0" + "resolve-from": "5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + } } }, "resolve-dir": { @@ -11591,9 +15785,9 @@ } }, "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "resolve-url": { "version": "0.2.1", @@ -11601,11 +15795,11 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "resolve-url-loader": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz", - "integrity": "sha512-K1N5xUjj7v0l2j/3Sgs5b8CjrrgtC70SmdCuZiJ8tSyb5J+uk3FoeZ4b7yTnH6j7ngI+Bc5bldHJIa8hYdu2gQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz", + "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==", "requires": { - "adjust-sourcemap-loader": "2.0.0", + "adjust-sourcemap-loader": "3.0.0", "camelcase": "5.3.1", "compose-function": "3.0.3", "convert-source-map": "1.7.0", @@ -11684,6 +15878,11 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rework": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", @@ -11732,6 +15931,79 @@ "inherits": "2.0.4" } }, + "rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "requires": { + "@types/estree": "0.0.45", + "@types/node": "14.14.3", + "acorn": "7.4.1" + } + }, + "rollup-plugin-babel": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", + "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", + "requires": { + "@babel/helper-module-imports": "7.12.1", + "rollup-pluginutils": "2.8.2" + } + }, + "rollup-plugin-terser": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", + "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", + "requires": { + "@babel/code-frame": "7.10.4", + "jest-worker": "24.9.0", + "rollup-pluginutils": "2.8.2", + "serialize-javascript": "4.0.0", + "terser": "4.8.0" + }, + "dependencies": { + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "requires": { + "merge-stream": "2.0.0", + "supports-color": "6.1.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "requires": { + "randombytes": "2.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "requires": { + "estree-walker": "0.6.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + } + } + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -11742,6 +16014,11 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -11755,7 +16032,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", "requires": { - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "safe-buffer": { @@ -11790,6 +16067,125 @@ "micromatch": "3.1.10", "minimist": "1.2.5", "walker": "1.0.7" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + } } }, "sanitize.css": { @@ -11830,11 +16226,29 @@ "shallow-clone": "3.0.1" } }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.5" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -11856,9 +16270,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "saxes": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", - "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", "requires": { "xmlchars": "2.2.0" } @@ -11878,7 +16292,7 @@ "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "requires": { "@types/json-schema": "7.0.6", - "ajv": "6.12.4", + "ajv": "6.12.6", "ajv-keywords": "3.5.2" } }, @@ -11907,17 +16321,17 @@ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "requires": { - "node-forge": "0.9.0" + "node-forge": "0.10.0" } }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "send": { "version": "0.17.1", @@ -11954,11 +16368,6 @@ } } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -11967,9 +16376,12 @@ } }, "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "2.1.0" + } }, "serve-index": { "version": "1.9.1", @@ -12124,36 +16536,16 @@ "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "optional": true }, "side-channel": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", "requires": { - "es-abstract": "1.18.0-next.0", + "es-abstract": "1.18.0-next.1", "object-inspect": "1.8.0" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", - "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", - "requires": { - "es-to-primitive": "1.2.1", - "function-bind": "1.1.1", - "has": "1.0.3", - "has-symbols": "1.0.1", - "is-callable": "1.2.0", - "is-negative-zero": "2.0.0", - "is-regex": "1.1.1", - "object-inspect": "1.8.0", - "object-keys": "1.1.1", - "object.assign": "4.1.0", - "string.prototype.trimend": "1.0.1", - "string.prototype.trimstart": "1.0.1" - } - } } }, "signal-exit": { @@ -12182,9 +16574,9 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "slice-ansi": { "version": "2.1.0", @@ -12314,12 +16706,13 @@ } }, "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", "requires": { "faye-websocket": "0.10.0", - "uuid": "3.4.0" + "uuid": "3.4.0", + "websocket-driver": "0.6.5" } }, "sockjs-client": { @@ -12348,7 +16741,7 @@ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", "requires": { - "websocket-driver": "0.7.4" + "websocket-driver": "0.6.5" } } } @@ -12397,13 +16790,18 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "requires": { "spdx-expression-parse": "3.0.1", - "spdx-license-ids": "3.0.5" + "spdx-license-ids": "3.0.6" } }, "spdx-exceptions": { @@ -12417,20 +16815,20 @@ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "requires": { "spdx-exceptions": "2.3.0", - "spdx-license-ids": "3.0.5" + "spdx-license-ids": "3.0.6" } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==" }, "spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "requires": { - "debug": "4.1.1", + "debug": "4.2.0", "handle-thing": "2.0.1", "http-deceiver": "1.2.7", "select-hose": "2.0.0", @@ -12442,7 +16840,7 @@ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "requires": { - "debug": "4.1.1", + "debug": "4.2.0", "detect-node": "2.0.4", "hpack.js": "2.1.6", "obuf": "1.1.2", @@ -12492,11 +16890,10 @@ } }, "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", "requires": { - "figgy-pudding": "3.5.2", "minipass": "3.1.3" } }, @@ -12506,9 +16903,24 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "requires": { + "escape-string-regexp": "2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } + }, + "stackframe": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" }, "static-extend": { "version": "0.1.2", @@ -12588,29 +17000,34 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", + "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", "requires": { - "astral-regex": "1.0.0", - "strip-ansi": "4.0.0" + "char-regex": "1.0.2", + "strip-ansi": "6.0.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "5.0.0" } } } }, + "string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -12627,29 +17044,49 @@ "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6", + "es-abstract": "1.17.7", "has-symbols": "1.0.1", "internal-slot": "1.0.2", "regexp.prototype.flags": "1.3.0", "side-channel": "1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", + "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6" + "es-abstract": "1.18.0-next.1" } }, "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", + "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6" + "es-abstract": "1.18.0-next.1" } }, "string_decoder": { @@ -12714,6 +17151,11 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -12728,24 +17170,12 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, "style-loader": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", - "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", "requires": { - "loader-utils": "1.4.0", - "schema-utils": "1.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "6.12.4", - "ajv-errors": "1.0.1", - "ajv-keywords": "3.5.2" - } - } + "loader-utils": "2.0.0", + "schema-utils": "2.7.1" } }, "stylehacks": { @@ -12753,8 +17183,8 @@ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", "requires": { - "browserslist": "4.14.1", - "postcss": "7.0.32", + "browserslist": "4.14.5", + "postcss": "7.0.35", "postcss-selector-parser": "3.1.2" }, "dependencies": { @@ -12778,6 +17208,30 @@ "has-flag": "3.0.0" } }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "requires": { + "has-flag": "4.0.0", + "supports-color": "7.2.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "4.0.0" + } + } + } + }, "svg-parser": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", @@ -12813,7 +17267,7 @@ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "requires": { - "ajv": "6.12.4", + "ajv": "6.12.6", "lodash": "4.17.20", "slice-ansi": "2.1.0", "string-width": "3.1.0" @@ -12859,6 +17313,37 @@ "inherits": "2.0.4" } }, + "temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" + }, + "tempy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", + "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", + "requires": { + "temp-dir": "1.0.0", + "type-fest": "0.3.1", + "unique-string": "1.0.0" + }, + "dependencies": { + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" + } + } + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "requires": { + "ansi-escapes": "4.3.1", + "supports-hyperlinks": "2.1.0" + } + }, "terser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", @@ -12867,24 +17352,36 @@ "commander": "2.20.3", "source-map": "0.6.1", "source-map-support": "0.5.19" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } } }, "terser-webpack-plugin": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz", - "integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", "requires": { - "cacache": "13.0.1", + "cacache": "15.0.5", "find-cache-dir": "3.3.1", - "jest-worker": "25.5.0", - "p-limit": "2.3.0", - "schema-utils": "2.7.1", - "serialize-javascript": "2.1.2", + "jest-worker": "26.6.1", + "p-limit": "3.0.2", + "schema-utils": "3.0.0", + "serialize-javascript": "5.0.1", "source-map": "0.6.1", - "terser": "4.8.0", + "terser": "5.3.8", "webpack-sources": "1.4.3" }, "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -12904,20 +17401,6 @@ "path-exists": "4.0.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "jest-worker": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", - "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", - "requires": { - "merge-stream": "2.0.0", - "supports-color": "7.2.0" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -12934,12 +17417,30 @@ "semver": "6.3.0" } }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "requires": { + "p-try": "2.2.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "requires": { "p-limit": "2.3.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "2.2.0" + } + } } }, "path-exists": { @@ -12955,97 +17456,48 @@ "find-up": "4.1.0" } }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "7.0.6", + "ajv": "6.12.6", + "ajv-keywords": "3.5.2" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "terser": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.8.tgz", + "integrity": "sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==", "requires": { - "has-flag": "4.0.0" + "commander": "2.20.3", + "source-map": "0.7.3", + "source-map-support": "0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } } } } }, "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "requires": { + "@istanbuljs/schema": "0.1.2", "glob": "7.1.6", - "minimatch": "3.0.4", - "read-pkg-up": "4.0.0", - "require-main-filename": "2.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "3.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "4.2.4", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2" - } - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.5.0", - "path-type": "3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "requires": { - "find-up": "3.0.0", - "read-pkg": "3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - } + "minimatch": "3.0.4" } }, "text-table": { @@ -13054,9 +17506,9 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" }, "through": { "version": "2.3.8", @@ -13133,12 +17585,11 @@ } }, "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" + "is-number": "7.0.0" } }, "toidentifier": { @@ -13156,9 +17607,9 @@ } }, "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", "requires": { "punycode": "2.1.1" } @@ -13176,22 +17627,53 @@ "glob": "7.1.6" } }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, "ts-pnp": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.6.tgz", - "integrity": "sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", + "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "requires": { + "@types/json5": "0.0.29", + "json5": "1.0.1", + "minimist": "1.2.5", + "strip-bom": "3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.5" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { "version": "3.17.1", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", "requires": { - "tslib": "1.13.0" + "tslib": "1.14.1" } }, "tty-browserify": { @@ -13218,13 +17700,18 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "1.2.1" } }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -13249,6 +17736,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "1.0.0" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -13310,10 +17805,18 @@ "imurmurhash": "0.1.4" } }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "1.0.0" + } + }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" }, "unpipe": { "version": "1.0.0", @@ -13396,13 +17899,25 @@ } }, "url-loader": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-2.3.0.tgz", - "integrity": "sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", "requires": { - "loader-utils": "1.4.0", - "mime": "2.4.6", - "schema-utils": "2.7.1" + "loader-utils": "2.0.0", + "mime-types": "2.1.27", + "schema-utils": "3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "requires": { + "@types/json-schema": "7.0.6", + "ajv": "6.12.6", + "ajv-keywords": "3.5.2" + } + } } }, "url-parse": { @@ -13420,17 +17935,17 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "requires": { - "inherits": "2.0.1" + "inherits": "2.0.3" }, "dependencies": { "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, @@ -13445,9 +17960,29 @@ "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", "requires": { "define-properties": "1.1.3", - "es-abstract": "1.17.6", + "es-abstract": "1.17.7", "has-symbols": "1.0.1", "object.getownpropertydescriptors": "2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.2.2", + "is-regex": "1.1.1", + "object-inspect": "1.8.0", + "object-keys": "1.1.1", + "object.assign": "4.1.1", + "string.prototype.trimend": "1.0.2", + "string.prototype.trimstart": "1.0.2" + } + } } }, "utila": { @@ -13470,6 +18005,23 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==" }, + "v8-to-istanbul": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-6.0.1.tgz", + "integrity": "sha512-PzM1WlqquhBvsV+Gco6WSFeg1AGdD53ccMRkFeyHRE/KRZaVacPOmQYP3EeVgDBtKD2BJ8kgynBQ5OtKiHCH+w==", + "requires": { + "@types/istanbul-lib-coverage": "2.0.3", + "convert-source-map": "1.7.0", + "source-map": "0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -13513,12 +18065,10 @@ } }, "w3c-xmlserializer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", - "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", "requires": { - "domexception": "1.0.1", - "webidl-conversions": "4.0.2", "xml-name-validator": "3.0.0" } }, @@ -13549,9 +18099,9 @@ "integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==", "dev": true, "requires": { - "@babel/core": "7.9.0", - "@babel/traverse": "7.11.5", - "@babel/types": "7.11.5", + "@babel/core": "7.12.3", + "@babel/traverse": "7.12.1", + "@babel/types": "7.12.1", "babylon": "7.0.0-beta.47", "webassembly-interpreter": "0.0.30" }, @@ -13572,6 +18122,28 @@ "requires": { "loader-utils": "1.4.0", "wasm-dce": "1.0.2" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + } } }, "watchpack": { @@ -13579,7 +18151,7 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", "requires": { - "chokidar": "3.4.2", + "chokidar": "3.4.3", "graceful-fs": "4.2.4", "neo-async": "2.6.2", "watchpack-chokidar2": "2.0.0" @@ -13594,12 +18166,60 @@ "chokidar": "2.1.8" }, "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "optional": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "optional": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + } + } + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "optional": true }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", @@ -13620,13 +18240,34 @@ "upath": "1.2.0" } }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, "fsevents": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { - "nan": "2.14.1" + "nan": "2.14.2" } }, "glob-parent": { @@ -13659,11 +18300,48 @@ "binary-extensions": "1.13.1" } }, - "normalize-path": { + "is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } }, "readdirp": { "version": "2.2.1", @@ -13675,6 +18353,15 @@ "micromatch": "3.1.10", "readable-stream": "2.3.7" } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } } } }, @@ -13704,21 +18391,21 @@ } }, "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz", - "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==", + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/wasm-edit": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "6.4.1", - "ajv": "6.12.4", + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "6.4.2", + "ajv": "6.12.6", "ajv-keywords": "3.5.2", "chrome-trace-event": "1.0.2", "enhanced-resolve": "4.3.0", @@ -13739,9 +18426,36 @@ }, "dependencies": { "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } }, "cacache": { "version": "12.0.4", @@ -13765,6 +18479,11 @@ "y18n": "4.0.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -13774,6 +18493,73 @@ "estraverse": "4.3.0" } }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.5" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -13782,12 +18568,32 @@ "yallist": "3.1.1" } }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { - "ajv": "6.12.4", + "ajv": "6.12.6", "ajv-errors": "1.0.1", "ajv-keywords": "3.5.2" } @@ -13824,6 +18630,15 @@ "worker-farm": "1.7.0" } }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -13863,10 +18678,49 @@ "which": "1.3.1" } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "3.0.0", + "resolve-cwd": "2.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "1.2.5" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "5.2.2", + "emojis-list": "3.0.0", + "json5": "1.0.1" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, "supports-color": { @@ -13890,19 +18744,26 @@ "mkdirp": "0.5.5", "range-parser": "1.2.1", "webpack-log": "2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } } }, "webpack-dev-server": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", - "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", "requires": { "ansi-html": "0.0.7", "bonjour": "3.5.0", "chokidar": "2.1.8", "compression": "1.7.4", "connect-history-api-fallback": "1.6.0", - "debug": "4.1.1", + "debug": "4.2.0", "del": "4.1.1", "express": "4.17.1", "html-entities": "1.3.1", @@ -13917,10 +18778,10 @@ "p-retry": "3.0.1", "portfinder": "1.0.28", "schema-utils": "1.0.0", - "selfsigned": "1.10.7", + "selfsigned": "1.10.8", "semver": "6.3.0", "serve-index": "1.9.1", - "sockjs": "0.3.19", + "sockjs": "0.3.20", "sockjs-client": "1.4.0", "spdy": "4.0.2", "strip-ansi": "3.0.1", @@ -13929,23 +18790,59 @@ "webpack-dev-middleware": "3.7.2", "webpack-log": "2.0.0", "ws": "6.2.1", - "yargs": "12.0.5" + "yargs": "13.3.2" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "1.1.0" + } + } + } }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } }, "chokidar": { "version": "2.1.8", @@ -13966,48 +18863,36 @@ "upath": "1.2.0" } }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { - "ansi-regex": "3.0.0" + "is-extendable": "0.1.1" } } } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "3.0.0" - } - }, "fsevents": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { - "nan": "2.14.1" + "nan": "2.14.2" } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -14027,6 +18912,15 @@ } } }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "requires": { + "pkg-dir": "3.0.0", + "resolve-cwd": "2.0.0" + } + }, "is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", @@ -14040,15 +18934,48 @@ "binary-extensions": "1.13.1" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "normalize-path": { + "is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } }, "readdirp": { "version": "2.2.1", @@ -14060,17 +18987,25 @@ "readable-stream": "2.3.7" } }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { - "ajv": "6.12.4", + "ajv": "6.12.6", "ajv-errors": "1.0.1", "ajv-keywords": "3.5.2" } @@ -14080,25 +19015,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -14107,33 +19023,13 @@ "has-flag": "3.0.0" } }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } + "is-number": "3.0.0", + "repeat-string": "1.6.1" } }, "ws": { @@ -14143,34 +19039,6 @@ "requires": { "async-limiter": "1.0.1" } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "3.0.0", - "get-caller-file": "1.0.3", - "os-locale": "3.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "4.0.0", - "yargs-parser": "11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" - } } } }, @@ -14181,6 +19049,13 @@ "requires": { "ansi-colors": "3.2.4", "uuid": "3.4.0" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + } } }, "webpack-manifest-plugin": { @@ -14203,6 +19078,19 @@ "jsonfile": "4.0.0", "universalify": "0.1.2" } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.2.4" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" } } }, @@ -14216,12 +19104,10 @@ } }, "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", "requires": { - "http-parser-js": "0.5.2", - "safe-buffer": "5.1.2", "websocket-extensions": "0.1.4" } }, @@ -14249,13 +19135,13 @@ "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", + "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", "requires": { "lodash.sortby": "4.7.0", - "tr46": "1.0.1", - "webidl-conversions": "4.0.2" + "tr46": "2.0.2", + "webidl-conversions": "6.1.0" } }, "which": { @@ -14285,164 +19171,200 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "workbox-background-sync": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz", - "integrity": "sha512-1uFkvU8JXi7L7fCHVBEEnc3asPpiAL33kO495UMcD5+arew9IbKW2rV5lpzhoWcm/qhGB89YfO4PmB/0hQwPRg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz", + "integrity": "sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-broadcast-update": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-4.3.1.tgz", - "integrity": "sha512-MTSfgzIljpKLTBPROo4IpKjESD86pPFlZwlvVG32Kb70hW+aob4Jxpblud8EhNb1/L5m43DUM4q7C+W6eQMMbA==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-5.1.4.tgz", + "integrity": "sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-build": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-4.3.1.tgz", - "integrity": "sha512-UHdwrN3FrDvicM3AqJS/J07X0KXj67R8Cg0waq1MKEOqzo89ap6zh6LmaLnRAjpB+bDIz+7OlPye9iii9KBnxw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-5.1.4.tgz", + "integrity": "sha512-xUcZn6SYU8usjOlfLb9Y2/f86Gdo+fy1fXgH8tJHjxgpo53VVsqRX0lUDw8/JuyzNmXuo8vXX14pXX2oIm9Bow==", "requires": { - "@babel/runtime": "7.11.2", + "@babel/core": "7.12.3", + "@babel/preset-env": "7.12.1", + "@babel/runtime": "7.12.1", "@hapi/joi": "15.1.1", + "@rollup/plugin-node-resolve": "7.1.3", + "@rollup/plugin-replace": "2.3.3", + "@surma/rollup-plugin-off-main-thread": "1.4.1", "common-tags": "1.8.0", - "fs-extra": "4.0.3", + "fast-json-stable-stringify": "2.1.0", + "fs-extra": "8.1.0", "glob": "7.1.6", "lodash.template": "4.5.0", "pretty-bytes": "5.4.1", + "rollup": "1.32.1", + "rollup-plugin-babel": "4.4.0", + "rollup-plugin-terser": "5.3.1", + "source-map": "0.7.3", + "source-map-url": "0.4.0", "stringify-object": "3.3.0", "strip-comments": "1.0.2", - "workbox-background-sync": "4.3.1", - "workbox-broadcast-update": "4.3.1", - "workbox-cacheable-response": "4.3.1", - "workbox-core": "4.3.1", - "workbox-expiration": "4.3.1", - "workbox-google-analytics": "4.3.1", - "workbox-navigation-preload": "4.3.1", - "workbox-precaching": "4.3.1", - "workbox-range-requests": "4.3.1", - "workbox-routing": "4.3.1", - "workbox-strategies": "4.3.1", - "workbox-streams": "4.3.1", - "workbox-sw": "4.3.1", - "workbox-window": "4.3.1" + "tempy": "0.3.0", + "upath": "1.2.0", + "workbox-background-sync": "5.1.4", + "workbox-broadcast-update": "5.1.4", + "workbox-cacheable-response": "5.1.4", + "workbox-core": "5.1.4", + "workbox-expiration": "5.1.4", + "workbox-google-analytics": "5.1.4", + "workbox-navigation-preload": "5.1.4", + "workbox-precaching": "5.1.4", + "workbox-range-requests": "5.1.4", + "workbox-routing": "5.1.4", + "workbox-strategies": "5.1.4", + "workbox-streams": "5.1.4", + "workbox-sw": "5.1.4", + "workbox-window": "5.1.4" }, "dependencies": { "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "requires": { "graceful-fs": "4.2.4", "jsonfile": "4.0.0", "universalify": "0.1.2" } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.2.4" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" } } }, "workbox-cacheable-response": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz", - "integrity": "sha512-Rp5qlzm6z8IOvnQNkCdO9qrDgDpoPNguovs0H8C+wswLuPgSzSp9p2afb5maUt9R1uTIwOXrVQMmPfPypv+npw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-5.1.4.tgz", + "integrity": "sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-core": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-4.3.1.tgz", - "integrity": "sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg==" + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.4.tgz", + "integrity": "sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg==" }, "workbox-expiration": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-4.3.1.tgz", - "integrity": "sha512-vsJLhgQsQouv9m0rpbXubT5jw0jMQdjpkum0uT+d9tTwhXcEZks7qLfQ9dGSaufTD2eimxbUOJfWLbNQpIDMPw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-5.1.4.tgz", + "integrity": "sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-google-analytics": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz", - "integrity": "sha512-xzCjAoKuOb55CBSwQrbyWBKqp35yg1vw9ohIlU2wTy06ZrYfJ8rKochb1MSGlnoBfXGWss3UPzxR5QL5guIFdg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz", + "integrity": "sha512-0IFhKoEVrreHpKgcOoddV+oIaVXBFKXUzJVBI+nb0bxmcwYuZMdteBTp8AEDJacENtc9xbR0wa9RDCnYsCDLjA==", "requires": { - "workbox-background-sync": "4.3.1", - "workbox-core": "4.3.1", - "workbox-routing": "4.3.1", - "workbox-strategies": "4.3.1" + "workbox-background-sync": "5.1.4", + "workbox-core": "5.1.4", + "workbox-routing": "5.1.4", + "workbox-strategies": "5.1.4" } }, "workbox-navigation-preload": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-4.3.1.tgz", - "integrity": "sha512-K076n3oFHYp16/C+F8CwrRqD25GitA6Rkd6+qAmLmMv1QHPI2jfDwYqrytOfKfYq42bYtW8Pr21ejZX7GvALOw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-5.1.4.tgz", + "integrity": "sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-precaching": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-4.3.1.tgz", - "integrity": "sha512-piSg/2csPoIi/vPpp48t1q5JLYjMkmg5gsXBQkh/QYapCdVwwmKlU9mHdmy52KsDGIjVaqEUMFvEzn2LRaigqQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-5.1.4.tgz", + "integrity": "sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-range-requests": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz", - "integrity": "sha512-S+HhL9+iTFypJZ/yQSl/x2Bf5pWnbXdd3j57xnb0V60FW1LVn9LRZkPtneODklzYuFZv7qK6riZ5BNyc0R0jZA==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-5.1.4.tgz", + "integrity": "sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-routing": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-4.3.1.tgz", - "integrity": "sha512-FkbtrODA4Imsi0p7TW9u9MXuQ5P4pVs1sWHK4dJMMChVROsbEltuE79fBoIk/BCztvOJ7yUpErMKa4z3uQLX+g==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-5.1.4.tgz", + "integrity": "sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "workbox-strategies": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-4.3.1.tgz", - "integrity": "sha512-F/+E57BmVG8dX6dCCopBlkDvvhg/zj6VDs0PigYwSN23L8hseSRwljrceU2WzTvk/+BSYICsWmRq5qHS2UYzhw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-5.1.4.tgz", + "integrity": "sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4", + "workbox-routing": "5.1.4" } }, "workbox-streams": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-4.3.1.tgz", - "integrity": "sha512-4Kisis1f/y0ihf4l3u/+ndMkJkIT4/6UOacU3A4BwZSAC9pQ9vSvJpIi/WFGQRH/uPXvuVjF5c2RfIPQFSS2uA==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-5.1.4.tgz", + "integrity": "sha512-xU8yuF1hI/XcVhJUAfbQLa1guQUhdLMPQJkdT0kn6HP5CwiPOGiXnSFq80rAG4b1kJUChQQIGPrq439FQUNVrw==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4", + "workbox-routing": "5.1.4" } }, "workbox-sw": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-4.3.1.tgz", - "integrity": "sha512-0jXdusCL2uC5gM3yYFT6QMBzKfBr2XTk0g5TPAV4y8IZDyVNDyj1a8uSXy3/XrvkVTmQvLN4O5k3JawGReXr9w==" + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-5.1.4.tgz", + "integrity": "sha512-9xKnKw95aXwSNc8kk8gki4HU0g0W6KXu+xks7wFuC7h0sembFnTrKtckqZxbSod41TDaGh+gWUA5IRXrL0ECRA==" }, "workbox-webpack-plugin": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-4.3.1.tgz", - "integrity": "sha512-gJ9jd8Mb8wHLbRz9ZvGN57IAmknOipD3W4XNE/Lk/4lqs5Htw4WOQgakQy/o/4CoXQlMCYldaqUg+EJ35l9MEQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-5.1.4.tgz", + "integrity": "sha512-PZafF4HpugZndqISi3rZ4ZK4A4DxO8rAqt2FwRptgsDx7NF8TVKP86/huHquUsRjMGQllsNdn4FNl8CD/UvKmQ==", "requires": { - "@babel/runtime": "7.11.2", - "json-stable-stringify": "1.0.1", - "workbox-build": "4.3.1" + "@babel/runtime": "7.12.1", + "fast-json-stable-stringify": "2.1.0", + "source-map-url": "0.4.0", + "upath": "1.2.0", + "webpack-sources": "1.4.3", + "workbox-build": "5.1.4" } }, "workbox-window": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-4.3.1.tgz", - "integrity": "sha512-C5gWKh6I58w3GeSc0wp2Ne+rqVw8qwcmZnQGpjiek8A2wpbxSJb1FdCoQVO+jDJs35bFgo/WETgl1fqgsxN0Hg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-5.1.4.tgz", + "integrity": "sha512-vXQtgTeMCUq/4pBWMfQX8Ee7N2wVC4Q7XYFqLnfbXJ2hqew/cU1uMTD2KqGEgEpE4/30luxIxgE+LkIa8glBYw==", "requires": { - "workbox-core": "4.3.1" + "workbox-core": "5.1.4" } }, "worker-farm": { @@ -14510,22 +19432,20 @@ } }, "write-file-atomic": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", - "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "requires": { - "graceful-fs": "4.2.4", "imurmurhash": "0.1.4", - "signal-exit": "3.0.3" + "is-typedarray": "1.0.0", + "signal-exit": "3.0.3", + "typedarray-to-buffer": "3.1.5" } }, "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "requires": { - "async-limiter": "1.0.1" - } + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" }, "xml-name-validator": { "version": "3.0.0", @@ -14537,14 +19457,6 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, - "xregexp": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", - "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", - "requires": { - "@babel/runtime-corejs3": "7.11.2" - } - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/clients/webassembly/react-example/package.json b/clients/webassembly/react-example/package.json index 4d7adf7c59..ea30f94e5c 100644 --- a/clients/webassembly/react-example/package.json +++ b/clients/webassembly/react-example/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@nymproject/nym-client-wasm": "^0.8.0", + "@nymproject/nym-client-wasm": "file:../pkg", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", @@ -11,7 +11,7 @@ "node-sass": "^4.14.1", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-scripts": "3.4.1", + "react-scripts": "^4.0.0", "reactstrap": "^8.5.1", "request": "^2.79.0" }, @@ -41,7 +41,6 @@ "hello-wasm-pack": "^0.1.0", "react-app-rewired": "^2.1.6", "wasm-loader": "^1.3.0", - "webpack": "^4.29.3", "webpack-cli": "^3.1.0" } } diff --git a/clients/webassembly/react-example/src/App.js b/clients/webassembly/react-example/src/App.js index dbe401acae..ed4fc0c596 100644 --- a/clients/webassembly/react-example/src/App.js +++ b/clients/webassembly/react-example/src/App.js @@ -42,8 +42,8 @@ class App extends React.Component { const wasm = await import('@nymproject/nym-client-wasm'); this.setState({ wasm }); // Set up identity and client - let directory = "https://qa-directory.nymtech.net"; - let _nymClient = new wasm.NymClient(directory); + let validator = "http://testnet-validator1.nymtech.net:8081"; + let _nymClient = new wasm.NymClient(validator); _nymClient.set_on_message(this.receivedMessage); // Start the Nym client. Connects to a Nym gateway via websocket. _nymClient = await _nymClient.initial_setup(); diff --git a/clients/webassembly/src/built_info.rs b/clients/webassembly/src/built_info.rs deleted file mode 100644 index 6a73d39ca8..0000000000 --- a/clients/webassembly/src/built_info.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The file has been placed there by the build script. - -#![allow(dead_code)] -include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index acaf085514..0a44aebe15 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -12,18 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::built_info; use crypto::asymmetric::{encryption, identity}; -use directory_client::DirectoryClient; use futures::channel::mpsc; use gateway_client::GatewayClient; use js_sys::Promise; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; +use nymsphinx::params::PacketMode; use nymsphinx::preparer::MessagePreparer; use rand::rngs::OsRng; use received_processor::ReceivedMessagesProcessor; -use std::convert::TryInto; use std::sync::Arc; use std::time::Duration; use topology::{gateway, NymTopology}; @@ -38,10 +36,12 @@ const DEFAULT_RNG: OsRng = OsRng; const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200); const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200); const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_PACKET_MODE: PacketMode = PacketMode::VPN; +const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000; #[wasm_bindgen] pub struct NymClient { - directory_server: String, + validator_server: String, // TODO: technically this doesn't need to be an Arc since wasm is run on a single thread // however, once we eventually combine this code with the native-client's, it will make things @@ -66,7 +66,7 @@ pub struct NymClient { #[wasm_bindgen] impl NymClient { #[wasm_bindgen(constructor)] - pub fn new(directory_server: String) -> Self { + pub fn new(validator_server: String) -> Self { // for time being generate new keys each time... let identity = identity::KeyPair::new_with_rng(&mut DEFAULT_RNG); let encryption_keys = encryption::KeyPair::new_with_rng(&mut DEFAULT_RNG); @@ -76,7 +76,7 @@ impl NymClient { identity: Arc::new(identity), encryption_keys: Arc::new(encryption_keys), ack_key: Arc::new(ack_key), - directory_server, + validator_server, message_preparer: None, // received_keys: Default::default(), topology: None, @@ -149,6 +149,8 @@ impl NymClient { client.self_recipient(), DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_AVERAGE_ACK_DELAY, + DEFAULT_PACKET_MODE, + Some(DEFAULT_VPN_KEY_REUSE_LIMIT), ); let received_processor = ReceivedMessagesProcessor::new( @@ -186,23 +188,21 @@ impl NymClient { .prepare_and_split_message(message_bytes, false, topology) .expect("failed to split the message"); - let mut socket_messages = Vec::with_capacity(split_message.len()); + let mut mix_packets = Vec::with_capacity(split_message.len()); for message_chunk in split_message { // don't bother with acks etc. for time being let prepared_fragment = message_preparer .prepare_chunk_for_sending(message_chunk, topology, &self.ack_key, &recipient) + .await .unwrap(); console_warn!("packet is going to have round trip time of {:?}, but we're not going to do anything for acks anyway ", prepared_fragment.total_delay); - socket_messages.push(( - prepared_fragment.first_hop_address, - prepared_fragment.sphinx_packet, - )); + mix_packets.push(prepared_fragment.mix_packet); } self.gateway_client .as_mut() .unwrap() - .batch_send_sphinx_packets(socket_messages) + .batch_send_mix_packets(mix_packets) .await .unwrap(); self @@ -233,27 +233,24 @@ impl NymClient { } pub fn get_full_topology_json(&self) -> Promise { - let directory_client_config = directory_client::Config::new(self.directory_server.clone()); - let directory_client = directory_client::Client::new(directory_client_config); + let validator_client_config = validator_client::Config::new(self.validator_server.clone()); + let validator_client = validator_client::Client::new(validator_client_config); future_to_promise(async move { - let topology = &directory_client.get_topology().await.unwrap(); + let topology = &validator_client.get_active_topology().await.unwrap(); Ok(JsValue::from_serde(&topology).unwrap()) }) } pub(crate) async fn get_nym_topology(&self) -> NymTopology { - let directory_client_config = directory_client::Config::new(self.directory_server.clone()); - let directory_client = directory_client::Client::new(directory_client_config); + let validator_client_config = validator_client::Config::new(self.validator_server.clone()); + let validator_client = validator_client::Client::new(validator_client_config); - match directory_client.get_topology().await { + match validator_client.get_active_topology().await { Err(err) => panic!(err), Ok(topology) => { - let nym_topology: NymTopology = topology - .try_into() - .ok() - .expect("this is not a NYM topology!"); - let version = built_info::PKG_VERSION; - nym_topology.filter_system_version(&version) + let nym_topology: NymTopology = topology.into(); + let version = env!("CARGO_PKG_VERSION"); + nym_topology.filter_system_version(version) } } } diff --git a/clients/webassembly/src/lib.rs b/clients/webassembly/src/lib.rs index da772b60c9..3c9a70fcd9 100644 --- a/clients/webassembly/src/lib.rs +++ b/clients/webassembly/src/lib.rs @@ -14,7 +14,6 @@ use wasm_bindgen::prelude::*; -pub(crate) mod built_info; #[cfg(target_arch = "wasm32")] mod client; diff --git a/common/client-libs/directory-client/models/src/presence/coconodes.rs b/common/client-libs/directory-client/models/src/presence/coconodes.rs deleted file mode 100644 index 7d1d74a4c6..0000000000 --- a/common/client-libs/directory-client/models/src/presence/coconodes.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crypto::asymmetric::identity; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; - -#[derive(Debug)] -pub enum ConversionError { - InvalidKeyError, -} - -impl From for ConversionError { - fn from(_: identity::SignatureError) -> Self { - ConversionError::InvalidKeyError - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CocoPresence { - pub location: String, - pub host: String, - pub pub_key: String, - pub last_seen: u64, - pub version: String, -} - -impl TryInto for CocoPresence { - type Error = ConversionError; - - fn try_into(self) -> Result { - Ok(topology::coco::Node { - location: self.location, - host: self.host, - pub_key: identity::PublicKey::from_base58_string(self.pub_key)?, - last_seen: self.last_seen, - version: self.version, - }) - } -} diff --git a/common/client-libs/directory-client/models/src/presence/gateways.rs b/common/client-libs/directory-client/models/src/presence/gateways.rs deleted file mode 100644 index dfa7b4d948..0000000000 --- a/common/client-libs/directory-client/models/src/presence/gateways.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crypto::asymmetric::{encryption, identity}; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use std::io; -use std::net::ToSocketAddrs; - -#[derive(Debug)] -pub enum ConversionError { - InvalidKeyError, - InvalidAddress(io::Error), -} - -impl From for ConversionError { - fn from(_: identity::SignatureError) -> Self { - ConversionError::InvalidKeyError - } -} - -impl From for ConversionError { - fn from(_: encryption::EncryptionKeyError) -> Self { - ConversionError::InvalidKeyError - } -} - -impl From for ConversionError { - fn from(err: io::Error) -> Self { - ConversionError::InvalidAddress(err) - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct GatewayPresence { - pub location: String, - pub client_listener: String, - pub mixnet_listener: String, - pub identity_key: String, - pub sphinx_key: String, - pub last_seen: u64, - pub version: String, -} - -impl TryInto for GatewayPresence { - type Error = ConversionError; - - fn try_into(self) -> Result { - let resolved_mix_hostname = self.mixnet_listener.to_socket_addrs()?.next(); - if resolved_mix_hostname.is_none() { - return Err(ConversionError::InvalidAddress(io::Error::new( - io::ErrorKind::Other, - "no valid socket address", - ))); - } - - Ok(topology::gateway::Node { - location: self.location, - client_listener: self.client_listener, - mixnet_listener: resolved_mix_hostname.unwrap(), - identity_key: identity::PublicKey::from_base58_string(self.identity_key)?, - sphinx_key: encryption::PublicKey::from_base58_string(self.sphinx_key)?, - last_seen: self.last_seen, - version: self.version, - }) - } -} diff --git a/common/client-libs/directory-client/models/src/presence/mixnodes.rs b/common/client-libs/directory-client/models/src/presence/mixnodes.rs deleted file mode 100644 index 9d44ae07c5..0000000000 --- a/common/client-libs/directory-client/models/src/presence/mixnodes.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crypto::asymmetric::encryption; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use std::io; -use std::net::ToSocketAddrs; - -#[derive(Debug)] -pub enum ConversionError { - InvalidKeyError, - InvalidAddress(io::Error), -} - -impl From for ConversionError { - fn from(_: encryption::EncryptionKeyError) -> Self { - ConversionError::InvalidKeyError - } -} - -impl From for ConversionError { - fn from(err: io::Error) -> Self { - ConversionError::InvalidAddress(err) - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MixNodePresence { - pub location: String, - pub host: String, - pub pub_key: String, - pub layer: u64, - pub last_seen: u64, - pub version: String, -} - -impl TryInto for MixNodePresence { - type Error = ConversionError; - - fn try_into(self) -> Result { - let resolved_hostname = self.host.to_socket_addrs()?.next(); - if resolved_hostname.is_none() { - return Err(ConversionError::InvalidAddress(io::Error::new( - io::ErrorKind::Other, - "no valid socket address", - ))); - } - - Ok(topology::mix::Node { - location: self.location, - host: resolved_hostname.unwrap(), - pub_key: encryption::PublicKey::from_base58_string(self.pub_key)?, - layer: self.layer, - last_seen: self.last_seen, - version: self.version, - }) - } -} diff --git a/common/client-libs/directory-client/models/src/presence/mod.rs b/common/client-libs/directory-client/models/src/presence/mod.rs deleted file mode 100644 index 1787da37a8..0000000000 --- a/common/client-libs/directory-client/models/src/presence/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod coconodes; -pub mod gateways; -pub mod mixnodes; -pub mod providers; -pub mod topology; - -pub use self::topology::{Topology, TopologyConversionError}; diff --git a/common/client-libs/directory-client/models/src/presence/topology.rs b/common/client-libs/directory-client/models/src/presence/topology.rs deleted file mode 100644 index 98d8219d18..0000000000 --- a/common/client-libs/directory-client/models/src/presence/topology.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{coconodes, gateways, mixnodes, providers}; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use topology::{MixLayer, NymTopology}; - -#[derive(Debug)] -pub enum TopologyConversionError { - CocoError(self::coconodes::ConversionError), - GatewayError(self::gateways::ConversionError), - MixError(self::mixnodes::ConversionError), -} - -impl From for TopologyConversionError { - fn from(err: self::coconodes::ConversionError) -> Self { - TopologyConversionError::CocoError(err) - } -} - -impl From for TopologyConversionError { - fn from(err: self::gateways::ConversionError) -> Self { - TopologyConversionError::GatewayError(err) - } -} - -impl From for TopologyConversionError { - fn from(err: self::mixnodes::ConversionError) -> Self { - TopologyConversionError::MixError(err) - } -} - -// Topology shows us the current state of the overall Nym network -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Topology { - pub coco_nodes: Vec, - pub mix_nodes: Vec, - pub mix_provider_nodes: Vec, - pub gateway_nodes: Vec, -} - -impl TryInto for Topology { - type Error = TopologyConversionError; - - fn try_into(self) -> Result { - use std::collections::HashMap; - - let mut coco_nodes = Vec::with_capacity(self.coco_nodes.len()); - for coco in self.coco_nodes.into_iter() { - coco_nodes.push(coco.try_into()?) - } - - let mut mixes = HashMap::new(); - for mix in self.mix_nodes.into_iter() { - let layer = mix.layer as MixLayer; - let layer_entry = mixes.entry(layer).or_insert(Vec::new()); - layer_entry.push(mix.try_into()?) - } - - let mut gateways = Vec::with_capacity(self.gateway_nodes.len()); - for gate in self.gateway_nodes.into_iter() { - gateways.push(gate.try_into()?) - } - - Ok(NymTopology::new(coco_nodes, mixes, gateways)) - } -} - -#[cfg(test)] -mod converting_mixnode_presence_into_topology_mixnode { - use super::*; - - #[test] - fn it_returns_error_on_unresolvable_hostname() { - use topology::mix; - - let unresolvable_hostname = "foomp.foomp.foomp:1234"; - - let mix_presence = mixnodes::MixNodePresence { - location: "".to_string(), - host: unresolvable_hostname.to_string(), - pub_key: "BnLYqQjb8K6TmW5oFdNZrUTocGxa3rgzBvapQrf8XUbF".to_string(), - layer: 0, - last_seen: 0, - version: "".to_string(), - }; - - let result: Result = mix_presence.try_into(); - assert!(result.is_err()); - } - - #[test] - #[cfg_attr(feature = "offline-test", ignore)] - fn it_returns_resolved_ip_on_resolvable_hostname() { - let resolvable_hostname = "nymtech.net:1234"; - - let mix_presence = mixnodes::MixNodePresence { - location: "".to_string(), - host: resolvable_hostname.to_string(), - pub_key: "BnLYqQjb8K6TmW5oFdNZrUTocGxa3rgzBvapQrf8XUbF".to_string(), - layer: 0, - last_seen: 0, - version: "".to_string(), - }; - - let result: Result = - mix_presence.try_into(); - result.unwrap(); - // assert!(result.is_ok()) - } -} diff --git a/common/client-libs/directory-client/src/lib.rs b/common/client-libs/directory-client/src/lib.rs deleted file mode 100644 index 2540157870..0000000000 --- a/common/client-libs/directory-client/src/lib.rs +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::requests::health_check_get::Request as HealthCheckRequest; -use crate::requests::metrics_mixes_get::Request as MetricsMixRequest; -use crate::requests::metrics_mixes_post::Request as MetricsMixPost; -use crate::requests::presence_coconodes_post::Request as PresenceCocoNodesPost; -use crate::requests::presence_gateways_post::Request as PresenceGatewayPost; -use crate::requests::presence_mixnodes_post::Request as PresenceMixNodesPost; -use crate::requests::presence_providers_post::Request as PresenceProvidersPost; -use crate::requests::presence_topology_get::Request as PresenceTopologyRequest; -use directory_client_models::metrics::{MixMetric, PersistedMixMetric}; -use directory_client_models::presence::{ - coconodes::CocoPresence, gateways::GatewayPresence, mixnodes::MixNodePresence, - providers::MixProviderPresence, -}; -use requests::{health_check_get::HealthCheckResponse, DirectoryGetRequest, DirectoryPostRequest}; - -pub use directory_client_models::{ - metrics, - presence::{self, Topology}, -}; - -pub mod requests; - -pub struct Config { - pub base_url: String, -} - -impl Config { - pub fn new(base_url: String) -> Self { - Config { base_url } - } -} - -pub trait DirectoryClient { - fn new(config: Config) -> Self; -} - -pub struct Client { - base_url: String, - reqwest_client: reqwest::Client, -} - -impl DirectoryClient for Client { - fn new(config: Config) -> Client { - let reqwest_client = reqwest::Client::new(); - Client { - base_url: config.base_url, - reqwest_client, - } - } -} - -impl Client { - async fn post( - &self, - request: R, - ) -> reqwest::Result { - self.reqwest_client - .post(&request.url()) - .json(request.json_payload()) - .send() - .await - } - - async fn get(&self, request: R) -> reqwest::Result { - self.reqwest_client - .get(&request.url()) - .send() - .await? - .json() - .await - } - - pub async fn get_healthcheck(&self) -> reqwest::Result { - let req = HealthCheckRequest::new(&self.base_url); - self.get(req).await - } - - pub async fn post_mix_metrics(&self, metrics: MixMetric) -> reqwest::Result { - let req = MetricsMixPost::new(&self.base_url, metrics); - self.post(req).await - } - - pub async fn get_mix_metrics(&self) -> reqwest::Result> { - let req = MetricsMixRequest::new(&self.base_url); - self.get(req).await - } - - pub async fn post_coconode_presence( - &self, - presence: CocoPresence, - ) -> reqwest::Result { - let req = PresenceCocoNodesPost::new(&self.base_url, presence); - self.post(req).await - } - - pub async fn post_gateway_presence( - &self, - presence: GatewayPresence, - ) -> reqwest::Result { - let req = PresenceGatewayPost::new(&self.base_url, presence); - self.post(req).await - } - - pub async fn post_mixnode_presence( - &self, - presence: MixNodePresence, - ) -> reqwest::Result { - let req = PresenceMixNodesPost::new(&self.base_url, presence); - self.post(req).await - } - - // this should be soft-deprecated as the whole concept of provider will - // be removed in the next topology rework - pub async fn post_provider_presence( - &self, - presence: MixProviderPresence, - ) -> reqwest::Result { - let req = PresenceProvidersPost::new(&self.base_url, presence); - self.post(req).await - } - - pub async fn get_topology(&self) -> reqwest::Result { - let req = PresenceTopologyRequest::new(&self.base_url); - self.get(req).await - } -} - -#[cfg(test)] -pub(crate) fn client_test_fixture(base_url: &str) -> Client { - Client { - base_url: base_url.to_string(), - reqwest_client: reqwest::Client::new(), - } -} diff --git a/common/client-libs/directory-client/src/requests/health_check_get.rs b/common/client-libs/directory-client/src/requests/health_check_get.rs deleted file mode 100644 index 93fbe91336..0000000000 --- a/common/client-libs/directory-client/src/requests/health_check_get.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{DirectoryGetRequest, DirectoryRequest}; -use serde::{Deserialize, Serialize}; - -const PATH: &str = "/api/healthcheck"; - -#[derive(Deserialize, Serialize)] -pub struct HealthCheckResponse { - pub ok: bool, -} - -pub struct Request { - base_url: String, - path: String, -} - -impl DirectoryRequest for Request { - fn url(&self) -> String { - format!("{}{}", self.base_url, self.path) - } -} - -impl DirectoryGetRequest for Request { - type JSONResponse = HealthCheckResponse; - - fn new(base_url: &str) -> Self { - Request { - base_url: base_url.to_string(), - path: PATH.to_string(), - } - } -} - -#[cfg(test)] -mod healthcheck_requests { - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("GET", "/api/healthcheck").with_status(400).create(); - let client = client_test_fixture(&mockito::server_url()); - let res = client.get_healthcheck().await; - assert!(res.is_err()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_200 { - use super::*; - - #[tokio::test] - async fn it_returns_a_response_with_200_status() { - let json = r#"{ - "ok": true - }"#; - let _m = mock("GET", "/api/healthcheck") - .with_status(200) - .with_body(json) - .create(); - let client = client_test_fixture(&mockito::server_url()); - let res = client.get_healthcheck().await; - assert!(res.unwrap().ok); - _m.assert(); - } - } -} diff --git a/common/client-libs/directory-client/src/requests/mod.rs b/common/client-libs/directory-client/src/requests/mod.rs deleted file mode 100644 index 39b712dff9..0000000000 --- a/common/client-libs/directory-client/src/requests/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod health_check_get; -pub mod metrics_mixes_get; -pub mod metrics_mixes_post; -pub mod presence_coconodes_post; -pub mod presence_gateways_post; -pub mod presence_mixnodes_post; -pub mod presence_providers_post; -pub mod presence_topology_get; - -use serde::{de::DeserializeOwned, Serialize}; - -pub(crate) trait DirectoryRequest { - fn url(&self) -> String; -} - -pub(crate) trait DirectoryGetRequest: DirectoryRequest { - // perhaps the name of this is not the best because it's technically not a JSON, - // but something that can be deserialised from JSON. - // I'm open to all suggestions on how to rename it - type JSONResponse: DeserializeOwned; - - fn new(base_url: &str) -> Self; -} - -pub(crate) trait DirectoryPostRequest: DirectoryRequest { - // Similarly this, it's something that can be serialized into a JSON - type Payload: Serialize + ?Sized; - - fn new(base_url: &str, payload: Self::Payload) -> Self; - fn json_payload(&self) -> &Self::Payload; -} diff --git a/common/client-libs/directory-client/src/requests/presence_coconodes_post.rs b/common/client-libs/directory-client/src/requests/presence_coconodes_post.rs deleted file mode 100644 index 71832cbed5..0000000000 --- a/common/client-libs/directory-client/src/requests/presence_coconodes_post.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{DirectoryPostRequest, DirectoryRequest}; -use crate::presence::coconodes::CocoPresence; - -const PATH: &str = "/api/presence/coconodes"; - -pub struct Request { - base_url: String, - path: String, - payload: CocoPresence, -} - -impl DirectoryRequest for Request { - fn url(&self) -> String { - format!("{}{}", self.base_url, self.path) - } -} - -impl DirectoryPostRequest for Request { - type Payload = CocoPresence; - fn json_payload(&self) -> &CocoPresence { - &self.payload - } - - fn new(base_url: &str, payload: Self::Payload) -> Self { - Request { - base_url: base_url.to_string(), - path: PATH.to_string(), - payload, - } - } -} - -#[cfg(test)] -mod presence_coconodes_post_request { - use super::*; - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("POST", PATH).with_status(400).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_coconode_presence(presence).await; - assert_eq!(400, result.unwrap().status()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_200 { - use super::*; - #[tokio::test] - async fn it_returns_a_response_with_201() { - let json = r#"{ - "ok": true - }"#; - let _m = mock("POST", PATH).with_status(201).with_body(json).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_coconode_presence(presence).await; - assert!(result.is_ok()); - _m.assert(); - } - } - - #[cfg(test)] - mod fixtures { - use crate::presence::coconodes::CocoPresence; - - pub fn new_presence() -> CocoPresence { - CocoPresence { - location: "foomp".to_string(), - host: "foo.com".to_string(), - pub_key: "abc".to_string(), - last_seen: 666, - version: "0.2.0".to_string(), - } - } - } -} diff --git a/common/client-libs/directory-client/src/requests/presence_gateways_post.rs b/common/client-libs/directory-client/src/requests/presence_gateways_post.rs deleted file mode 100644 index bd12a83f6d..0000000000 --- a/common/client-libs/directory-client/src/requests/presence_gateways_post.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{DirectoryPostRequest, DirectoryRequest}; -use crate::presence::gateways::GatewayPresence; - -const PATH: &str = "/api/presence/gateways"; - -pub struct Request { - base_url: String, - path: String, - payload: GatewayPresence, -} - -impl DirectoryRequest for Request { - fn url(&self) -> String { - format!("{}{}", self.base_url, self.path) - } -} - -impl DirectoryPostRequest for Request { - type Payload = GatewayPresence; - fn json_payload(&self) -> &GatewayPresence { - &self.payload - } - - fn new(base_url: &str, payload: Self::Payload) -> Self { - Request { - base_url: base_url.to_string(), - path: PATH.to_string(), - payload, - } - } -} - -#[cfg(test)] -mod presence_gateways_post_request { - use super::*; - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("POST", PATH).with_status(400).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_gateway_presence(presence).await; - assert_eq!(400, result.unwrap().status()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_200 { - use super::*; - #[tokio::test] - async fn it_returns_a_response_with_201() { - let json = r#"{ - "ok": true - }"#; - let _m = mock("POST", PATH).with_status(201).with_body(json).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_gateway_presence(presence).await; - assert!(result.is_ok()); - _m.assert(); - } - } - #[cfg(test)] - mod fixtures { - use crate::presence::gateways::GatewayPresence; - - pub fn new_presence() -> GatewayPresence { - GatewayPresence { - location: "foomp".to_string(), - client_listener: "foo.com".to_string(), - mixnet_listener: "foo.com".to_string(), - identity_key: "def".to_string(), - sphinx_key: "abc".to_string(), - last_seen: 0, - version: "0.1.0".to_string(), - } - } - } -} diff --git a/common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs b/common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs deleted file mode 100644 index 6a3af4eb00..0000000000 --- a/common/client-libs/directory-client/src/requests/presence_mixnodes_post.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{DirectoryPostRequest, DirectoryRequest}; -use crate::presence::mixnodes::MixNodePresence; - -const PATH: &str = "/api/presence/mixnodes"; - -pub struct Request { - base_url: String, - path: String, - payload: MixNodePresence, -} - -impl DirectoryRequest for Request { - fn url(&self) -> String { - format!("{}{}", self.base_url, self.path) - } -} - -impl DirectoryPostRequest for Request { - type Payload = MixNodePresence; - fn json_payload(&self) -> &MixNodePresence { - &self.payload - } - - fn new(base_url: &str, payload: Self::Payload) -> Self { - Request { - base_url: base_url.to_string(), - path: PATH.to_string(), - payload, - } - } -} - -#[cfg(test)] -mod presence_mixnodes_post_request { - use super::*; - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("POST", PATH).with_status(400).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_mixnode_presence(presence).await; - assert_eq!(400, result.unwrap().status()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_200 { - use super::*; - #[tokio::test] - async fn it_returns_a_response_with_201() { - let json = r#"{ - "ok": true - }"#; - let _m = mock("POST", PATH).with_status(201).with_body(json).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_mixnode_presence(presence).await; - assert!(result.is_ok()); - _m.assert(); - } - } - - #[cfg(test)] - mod fixtures { - use crate::presence::mixnodes::MixNodePresence; - - pub fn new_presence() -> MixNodePresence { - MixNodePresence { - location: "foomp".to_string(), - host: "foo.com".to_string(), - pub_key: "abc".to_string(), - layer: 1, - last_seen: 0, - version: "0.1.0".to_string(), - } - } - } -} diff --git a/common/client-libs/directory-client/src/requests/presence_providers_post.rs b/common/client-libs/directory-client/src/requests/presence_providers_post.rs deleted file mode 100644 index c8583596db..0000000000 --- a/common/client-libs/directory-client/src/requests/presence_providers_post.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{DirectoryPostRequest, DirectoryRequest}; -use crate::presence::providers::MixProviderPresence; - -const PATH: &str = "/api/presence/mixproviders"; - -pub struct Request { - base_url: String, - path: String, - payload: MixProviderPresence, -} - -impl DirectoryRequest for Request { - fn url(&self) -> String { - format!("{}{}", self.base_url, self.path) - } -} - -impl DirectoryPostRequest for Request { - type Payload = MixProviderPresence; - fn json_payload(&self) -> &MixProviderPresence { - &self.payload - } - - fn new(base_url: &str, payload: Self::Payload) -> Self { - Request { - base_url: base_url.to_string(), - path: PATH.to_string(), - payload, - } - } -} - -#[cfg(test)] -mod presence_providers_post_request { - use super::*; - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("POST", PATH).with_status(400).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_provider_presence(presence).await; - assert_eq!(400, result.unwrap().status()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_200 { - use super::*; - #[tokio::test] - async fn it_returns_a_response_with_201() { - let json = r#"{ - "ok": true - }"#; - let _m = mock("POST", PATH).with_status(201).with_body(json).create(); - let client = client_test_fixture(&mockito::server_url()); - let presence = fixtures::new_presence(); - let result = client.post_provider_presence(presence).await; - assert!(result.is_ok()); - _m.assert(); - } - } - #[cfg(test)] - mod fixtures { - use crate::presence::providers::MixProviderPresence; - - pub fn new_presence() -> MixProviderPresence { - MixProviderPresence { - location: "foomp".to_string(), - client_listener: "foo.com".to_string(), - mixnet_listener: "foo.com".to_string(), - pub_key: "abc".to_string(), - registered_clients: vec![], - last_seen: 0, - version: "0.1.0".to_string(), - } - } - } -} diff --git a/common/client-libs/directory-client/src/requests/presence_topology_get.rs b/common/client-libs/directory-client/src/requests/presence_topology_get.rs deleted file mode 100644 index ff44fb9bfb..0000000000 --- a/common/client-libs/directory-client/src/requests/presence_topology_get.rs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{DirectoryGetRequest, DirectoryRequest}; -use crate::presence::Topology; - -const PATH: &str = "/api/presence/topology"; - -pub struct Request { - base_url: String, - path: String, -} - -impl DirectoryRequest for Request { - fn url(&self) -> String { - format!("{}{}", self.base_url, self.path) - } -} - -impl DirectoryGetRequest for Request { - type JSONResponse = Topology; - - fn new(base_url: &str) -> Self { - Request { - base_url: base_url.to_string(), - path: PATH.to_string(), - } - } -} - -#[cfg(test)] -mod topology_requests { - use super::*; - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("GET", PATH) - .with_status(400) - .with_body("bad body") - .create(); - let client = client_test_fixture(&mockito::server_url()); - let result = client.get_topology().await; - assert!(result.is_err()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_200 { - use super::*; - - #[tokio::test] - async fn it_returns_a_response_with_200_status_and_a_correct_topology() { - let json = fixtures::topology_response_json(); - let _m = mock("GET", PATH).with_status(200).with_body(json).create(); - let client = client_test_fixture(&mockito::server_url()); - let result = client.get_topology().await; - assert_eq!( - 1_575_915_097_085_539_300, - result.unwrap().coco_nodes.first().unwrap().last_seen - ); - _m.assert(); - } - } - - #[cfg(test)] - pub mod fixtures { - #[cfg(test)] - pub fn topology_response_json() -> String { - r#"{ - "cocoNodes": [ - { - "location": "unknown", - "host": "3.8.244.109:4000", - "pubKey": "AAAAAAAAAAEKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEFuhRpy4HfcuxwcRI9sIWMo_LVmbk19g1gfMRlBrmZqoEQL6rDApVLZ9eMp-5IQK8WLlZpWf4Zjy7kZolARAyp_rHUQkH4PrDjgoPrKbm6qK_iejYpL7qx28Q3VeInMpwMIMaSbbW9y36sEVtGc2I0Iu5vS0sp8ESiVlQ5NaBz72deZ8oKJJ4IEPPHP99-b0UQX80fVIrNM88mMzKy0bHri9NFlmIG-e0G1cqmw_ry3XWGQkcr1M5RuNa6oX50w5QawAEVxd5FP5bE8bS4x54Csof11sQWUTwMp6Q7_3H7ZCTSlKSqujlOhmfqSHfGPO2sDIYPHDhDzjakZpKAZWWhn_hiR6DfPpomQ01ZYUhVKKSMxz7_VPjsQplP0bZXA2gfnkADUN8UQ0N9g_usIw73r4aZsOviMsRM8oByvsjVfUWc4_HTLSdnQyImFkHz9CiCmrIYL2dYQRePRatWggvBAyeRzntxI4jDqLKiBdi54ZlAKgV6MCRaJ7Bu7BtmLXrtK4sawAED3QYxuvOSZrbZdUr4yG-U9yVvJ9Klkf-5Mo4EYp3qTL2KBB6_LrZepjAQqp486YkZ03mTIezcsZ48EboXVTWKBZ3QnTI5tX-j4gGxQb7klOJc97qJkDxsvpz4F0ChgCUIZhpIItWHia7_R3Gi-b5siLIdQdUho9isn3kiDGm6t0NED2Bgy3ZxxQwzqsBZm4kPr2_fPX4YyvIoP9895YcGjZyE5iiRC_TE41RJmB1GZYdxegTMq3lNDllKgiqaiPgawAEJASDkmZHTwlg9YOev5OWpQD-FnhPkqVNo_QcDyRu9eoGcWSGFp2sYqjG2SpmiXq0VNnAO7AcKxRzDFu7TjfhlU3Kt0uTKIcrWVU1zFNbJNMjYEq90pp50nowwx8INz20IXET2ZNX6kIXYFCsEvPLZFlG2OoL6xg3uQS1qMl3lIS_VxdO_JfVe0rT65WsJ_P4Nkc1jYiuNPHY6d_iFO0BVYqX0sOCX73GC_TT13BR0jnPwDAVw0rGtYHsXBb8TKOsawAEZIClauuT1V3qOZnb7uRZhFXO-PKTxgc1LCzJt2ChOrMZaBpjlkf3IPpJ2UF4JH4kGaDeBf2k_S-FLAs3drK21efbi5P6_a4QTxAiiRimXGoQIyvOg462s6kP_ZRFufo8YYQHS4olaOeqU4564dNskg_uBPsFMz_2GNOhmn_15cJqP1jfkyD49Z16GTS5YLHgVl9bJKqyvLuypsToLbt1BJzipEP0L2OohuRm-_MvqvwwWKyjNQsubgee1K728d9AawAEBkGggcNVCtXyhoSqi3_w0tVxtkAYeud8sBeAtZHGs06me_QL8co0MFLlO-zdkUb4ZBq08rFEbgLOma8_3whleM8NIPaHNISp1q3IsIhB5zdXcZoGsqLixODBFHtID3YEHAlr4f9T_yh11yJ95xGCl_6Y37hpwLQVGyrfSfccM24mVFqnV3TT5Wdq3ile-jesUx1Q2G1yK_xVqc6itmk-kDuBjyZgzYi1-jsIXAjnhM9G7t8J_Bv5yGGZhLK2dCzM=", - "type": "validator", - "lastSeen": 1575915097085539300, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.129.61:4000", - "pubKey": "AAAAAAAAAAMKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEEv6RMevAQmLGkeK0uJKnMPPAtm8GgXjWSQijYdnxlPh5SJSNeJUbPZKWFFWdk8yIFXKa8jnzETtdGFKgUUt5AVUDpTBmEdwaHCzlFhXrttshy0V5OhPUlV8cGABmxbagMYm0bFPg0r-snSkrB9YG6wqJYQVeIMOCGYCPbHmDA8R_0-h8VkRKWs1d9KvQOK4kShqgZtYN71KJW8uDE4q2jsGDVvxFt1AgmU9b93xsXF17KrpZy5WxlLZ73HtnTD_oawAED4vd_rK-Kx_n8x_OdDiiEOPUlYDlDCQUqenU9XHKH3B6ijfkJ368wd3LDDVStjDwNORrAyUSw_VlSNUpd1XLC8d17gTaIq5ZI2fWuwwZaoN1JCsYU8fQ6USgtIehQX7IPP8EkFuNmuCBCmpr4schtYniGe9J8Q4dsV-TYPr2uLJkdx1r7luzF--I22k7NfQQM14QDci_0kgrgmZ54CJGkjXyOhCppBXg3fqLC6aFvT3ZocfiiXBJt0huGgPMDtYsawAECLh8KUdNsDolERwJ8v04bS5jI_KKf7uUnCHWuCELwbJSUI3OK1ufS1qSpauvSzVQSbrhEzrEfwQn4VtxQxJlX4UdDU-R-hafiZvVC6DLLAbuORBAC3FScn9W58CnezH4DvCp_w7nftDfdxeuungbZT9XaxS3iNC6PnFsWF6WM3DxMwrzOrFe6wEEoTSPe1mcUDrtwM5UksIvJr6MBRAXrdl0IdBTQr7cLwKe_KYi4siwdjfJEJtOh7oxQBxBg2UkawAEJAPZK2Gg2MQwpxdDT24lNQHF7FVfkO_LuhJwn0RbwNDSVeA4P6-tWL5TkCpqr8xYHfwQ6Z3ILfpGCZr8PspwIoRzqZHQ16f8Pq9xnr0hLEI9BOQU0FS2EtuyPgju5iwsAJAfehUzu6kNLphuLGsXoIZdXDG5mbylwh9JzAVXTwgaR0hNqyXVJxgbt7jcYaSEBFcMGV-hjXyVVNzBleE-G9o_noI_KWU4Ce7K-qOMcewMKfy_VEw-gVaD6dHz6AMoawAEE9XuOLwRttvKybAssZ9gsK-_YRUwuFOeRDIr3NX___9bx6pCc18adCIlH_8EJWFwXZ05ZpNNE88mYx7ZQ3aqaArZJRoWeZeKhqH_s05V10xbzkYX71G5cqz--8vr9ZlQRb2BeETF_Tdq_PLk7qbT8WTGIoq7ZwyDRQTgzvkCgyzj_hBLh2o7sSVNgUo38SFUTMn7YtvVFYlSrTDE3WKE-T-nh5SWdDBxgDTc3Bw8JpzNH-WkoJ4Lim7sB4Op1gEUawAEW4-kenlffwsNr_3b3aV0YuusLpxB03sxPzQ5B0CWNiVtbja1Z4tWhKGUUrdq_eUgMV0y5Of-BqNi5FspAQnhJBFSSxtOzRGV1h3qyUTksfZyed9z8zPI-ZPP9XXm7hYgJgDz_kxte-NfS9UG9q5AZetHUN4kGxXutjjzfUQZ9yTvhBKgKgTI2Dp_R_jZrWQ8F1BoWzIJzjddT1K2MvCQEkARYw08isbOeFmCwgVUcjxYZO45WyOmLQA7QJRL9WvA=", - "type": "validator", - "lastSeen": 1575915097388409000, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.222.1:4000", - "pubKey": "AAAAAAAAAAQKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAECh9xcxpjOp1r7kiNIgrI9GgAlvXwgHkTchOxUiyOzTq6FDWdGN64KiC3NDeyGTg8FmzvGzS3jREeJqOdr4G9ZGtWkauAITgLFiH62t-YntRslhr8_1shxlmzKiNKJN_QFflEq79pZIlWtp3N8LIHMvXRtl-zt2DMze4s02XDmEkviyVE4CkQUDtCc-2MfPT4JcmEFqtFIxjrXn18SbYg3c6XUQHsGIkuDrKuCTRlpC8kvmM0uVoIeWdmwDlZk4jUawAEJhRwK5ozjqIWRP1bFzBPS9VhaJnfKU9PeFYtN5beiAHrYr2ylIB3yDfmAQUdKDowDUm5nfJATejEjEnrTGxh70QtfoNV391rSns3F71tBwY62KLaNr8qnVfeSFHV3FcQTMHHF_8mDb5_11Rj6aiMvW0y6eetHo7CDPMdEyDPmok_U2ZM5BzOUnwjT21HtnvcKxKKwHJ_QGfnAHPyDIhNOMgxJCrVazOidLCHeYGpyCLw1ipeTyKOQX0_ByB8dH6AawAEGV1GuF5SSlT67B1ityPJK2ZwXjeeKB4gGdCG3qRtWxLTZfGhVm7YAYm2f5tw_wrsJAZ9FubVhateGg0ZN67NxZtsvOOejXz6743f7ijnQopPgd_8pH-iVf6BEcSO8ZdcHxNRUTayzjVLs99bwMo2zaPevW4X4G_bN4mh---aPkdGYHwaiklzUhqJ-eqycrYAFyjyEXaPBXLQm1rpczqluNvnKbd8Q9LZWukgm7_uWv_HxufIvdWgoq8bAt78UU3oawAEP9VDehhqrQG5-WHMB66XVxo1TgMM8aVV0SwAq3lCRkpiFBz_9kw8T1F9Hx2AiNrEGT1QLbdMkpms1cG_5gBBahQofdt_NmUs1jfTFXY9iyMy1Q7A6ZYaLP8Z6q-orc1cKqySY-BJZQ_CpGFfXS0OVniFDQ6v78ytPK7K-yRgT1PxFgm3rZqrG0Tjbrpsg2PUL5S5fuXfMhUosP0uoLj0D1guWAR9Y7kfFBIXaTSFMoa8fghVBUTRNhK9f72a8SxQawAEOiv71taLjKqaaWQ_QjcDhWbvjG1EnsCyI0toNjGkcF19x4Vk-5NC96_4ioUGz404IC0XN03roRnibRT_78D9vZFVCWCqve9EjdF5TcApx03zIP4JT2g2q0MKIGgGrwt4Pz6LO6yOfMm7B8Yraps8IV-nP1w7K1m9XKP_FvH8egl5GHJe-_omlC2YyL_b28jMLENbxDFD-3KPjZFBhSLrRukX2PlayYTwEiTtokA2R9_11vQvJgP8KFEjGHg6zsAMawAEBn2H_hz2knb8ltnpEA5YSKVcV3nUtojkCNi_WUz7xUKd7efw1oI_lbnKrS7HkyC0JkQUZ1pCWUlSXNmgjMEhsn823a1LFzpV7rOv4vayYvvFX61hB9R78VjpyxJiYpDwRZLiUY3AK4WY8NqFDbjXR7rT4CkFHEf-VhSQQ8ZNvlpod1nmeVQVizHH9e7Tq7wsWz-LWEk3Hx6LmcrgDsL79LZYG9JXU5IdvG8RvLNx9cSwEI8yxcchpISAaot7UoYQ=", - "type": "validator", - "lastSeen": 1575915094734973000, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.102.214:4000", - "pubKey": "AAAAAAAAAAIKwAECSqKy8I8KkSYIBSctxRBRxuR61PpAOwK0UQtkeuPRdwusAyaoBbvv1IBWyMEhvbgT4CtgUnGfYH2s06CIJ09lWWvQ0Jkgthq12mG73H9QSTNM8RITlF1X5ax9BV0EK34M5dUncn1uEYzJzcbaLjUarf2bqoy906dtQpppUWDRLJI6ycw7rKKJ4ZNUhgi4KAEGBsSgLqc0zDKs0rArwouZyz4ofoWnY68mdJKrVy6Zqz83DSdc7B2hqqkHX_Bfeb4SwAEOVCUN3EwiVroS5-TOq2o7hYSxphK9X0G23N-IBZ0Tr1Rl8XEiJ-OEy0rqnAKwmhAZJWnx3u8oXqbZtOWIZmzQSpcoxhgwfhdmTZJCqT2RVzZyeFItX4sVeilEP3z2xdsJs8-a1kg6UZnx1s1BNLBo7eZrreZygWojPCIDBn03fSAflXoVc5PpY2CGy5MA_IgWgSYBHDdoZEtigp_amjqK7Us44Db20XpLxMXfbahiqa7WKNnMgi6Ca2H67VtaaD8awAEF3zbE1nZRAa7a8vbU25c80YBYJBaW8P6FwXQI-K0Xk5MakwYeMMnIrm6w6IS_0XAO5YlD453GLqnxY8H1BEnRpfOnT7PE4el9mJ8MuYQMo6R2up0lGCmYM0YA9FORjroM3ng69SEPfJPCReG7LfJkERl_m2U403ertDRBYrlqCDagDfyI500srBcMrjSvV3oNouyyx3yZUrjLQfbHhDteQFsYdmakJs8Y-Q9-5MXCcrz6Qa4xwv522Euv0CCxkHcawAEYjfsU_zDhUZA1ey1aquWXlFOnx-iEALqxW1slDYHwQ1M2SILc-v_E6i1doa5e_bAZHVezBHFAlaNAVedNyHFFJxYAqAK3hbzbvl2glw3Q6h_rTXElymloqtaqVFIJ-oUWWOHsZBmu8EDA-HzvGCiBa_GbRaVfh2lE4ObeMXoJrEm_5dbxxeEic2l3IYeIz40N9ooQQOkQcOZdY4AXWYCavIAwWEJBjLtptJgCLu9a_zM1S5GsiyJHpdDs46WbP0EawAEWZ-95Sf0YAHujxRNLdXgpqe0ZF8loVwzZfvyMvqaxF1Ug274BqHuY_c5NdPAzuqoTwjfEn8NKEoaNqlumM75FUYbaTd7mXvk4WVYWjVnkO40dfQjRB7DYhvj0LBlbndAJ4wJIA2ilPYgjZsXVbNNh3e2j3u9eABd0VaFMbSb8Sz5_31r8HzoWmPJs3HiyuyANGFUA6CvAnMN6K3b-D8BhFZU_nPUTgu80o8_n6LQt-XWbaC_mTHzsnOjzBiPJxlYawAEW3bmOEtStH2T8q7vMkhchImp2-hg9MFYGBmEe9sSByTn3NUf8eksqXOC1dUjHkXoZm298FgUYLkNdnlxWpf993j5mEDoFxjcTB7scBD7k6nu6Nrs_wK0-seS8gsHrx9UK7GwAsi10q82Cm4PFyAtrWjmy_d9WLHuZt6VIOKunTs8cf0FwNUiMcvZsruqIFJcP7iWxdiFdUkh65P_iCz1ZEjJcj2GEZoq4v3a3by1aizGPaaiKc1jd_T-XJg_YpncawAEWnstu5b9WiZv0x8xfsiMk6YRlU0Cnj5svxLLXz_8drvwAa--GBY5yH0ke2EM6udMEi2EPeFcGTe6Sjs0YEhSbY7Uad_8suD2J4tIWJSWBbiyvh7rSqzv57m7BlsVcHfQJn_wNH-UlC9xkx8vg-LwfN8_FlxvHNPTc7XZG3lKYbwpUWlZxAziOYT1VQ-2K2bQQBBMdix-ht_SjccL1Dc2dP5kDazQ8yZV_8xnyeheazEedWe63uutfkHlZRg9YwP8=", - "type": "validator", - "lastSeen": 1575915094967382800, - "version": "0.1.0" - } - ], - "mixNodes": [ - { - "location": "unknown", - "host": "35.176.155.107:1789", - "pubKey": "zSob16499jT7C3S3ky4GihNOjlU6aLfSRkf1xAxOwV0=", - "layer": 3, - "lastSeen": 1575915096805374500, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "18.130.86.190:1789", - "pubKey": "vCdpFc0NvW0NSqsuTxtjFtiSY35aXesgT3JNA8sSIXk=", - "layer": 1, - "lastSeen": 1575915097370376000, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.10.22.152:1789", - "pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=", - "layer": 1, - "lastSeen": 1575915097639423500, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "35.178.213.77:1789", - "pubKey": "nkkrUjgL8UJk05QydvWvFSvtRB6nmeV8RMvH5540J3s=", - "layer": 2, - "lastSeen": 1575915097895166500, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "52.56.99.196:1789", - "pubKey": "whHuBuEc6zyOZOquKbuATaH4Crml61V_3Y-MztpWhF4=", - "layer": 2, - "lastSeen": 1575915096255174700, - "version": "0.1.0" - }, - { - "location": "unknown", - "host": "3.9.12.238:1789", - "pubKey": "vk5Sr-Xyi0cTbugACv8U42ZJ6hs6cGDox0rpmXY94Fc=", - "layer": 3, - "lastSeen": 1575915096497827600, - "version": "0.1.0" - } - ], - "mixProviderNodes": [ - { - "location": "unknown", - "clientListener": "3.8.176.11:8888", - "mixnetListener": "3.8.176.11:9999", - "pubKey": "54U6krAr-j9nQXFlsHk3io04_p0tctuqH71t7w_usgI=", - "registeredClients": [ - { - "pubKey": "zOqdJFH49HcgGSCRnmbXGzovnwRLEPN0YGN1SCafTyo=" - }, - { - "pubKey": "fy9xo69hZ2UJ9uxhIS1YzKHZsH8saV-02AiyCNXPNUc=" - }, - { - "pubKey": "COGdpfhmzNGR6YX820GqJIkjOihL8mr6-h-d3JlTDFA=" - } - ], - "lastSeen": 1575915097358694100, - "version": "0.1.0" - }, - { - "location": "unknown", - "clientListener": "3.8.176.12:8888", - "mixnetListener": "3.8.176.12:9999", - "pubKey": "sA-sxi038pEbGy4lgZWG-RdHHDkA6kZzu44G0LUxFSc=", - "registeredClients": [ - { - "pubKey": "7fbk4oGQNlTW-tnWjVz8rWtKrtAicTsiNWgO98sqMyk=" - }, - { - "pubKey": "w1bfLpnd3rWu5JczB0nQfnE2S6nUCbx2AA7HDE48DQo=" - } - ], - "lastSeen": 1575915097869025000, - "version": "0.1.0" - } - ], - "gatewayNodes": [ - { - "location": "unknown", - "clientListener": "3.8.176.11:8888", - "mixnetListener": "3.8.176.11:9999", - "identityKey": "B9xz9V6jpp1fEbDkeyR5f8miorw9bzXGKoMbKnaxkD41", - "sphinxKey": "3KCpz1HCD8DqnQjemT1uuBZipmHFXM4V5btxLXwvM1gG", - "registeredClients": [ - { - "pubKey": "zOqdJFH49HcgGSCRnmbXGzovnwRLEPN0YGN1SCafTyo=" - }, - { - "pubKey": "fy9xo69hZ2UJ9uxhIS1YzKHZsH8saV-02AiyCNXPNUc=" - } - ], - "lastSeen": 1575915097358694100, - "version": "0.1.0" - }, - { - "location": "unknown", - "clientListener": "3.8.176.12:8888", - "mixnetListener": "3.8.176.12:9999", - "identityKey": "CdqJCedY5d1geJNDjUqnEx8zF7mKjb6PCZ6k3T6xhxD", - "sphinxKey": "BnLYqQjb8K6TmW5oFdNZrUTocGxa3rgzBvapQrf8XUbF", - "registeredClients": [ - { - "pubKey": "UE-7r6-bpw0b4T3GxOBVxlg02psx23DF2p5Tuf-OBSE=" - }, - { - "pubKey": "UnZuLpzq64_EPtIcr1Fd-5AESBCBLFnDMDsjUaOqrUA=" - } - ], - "lastSeen": 1575915097869025000, - "version": "0.1.0" - } - ] - }"#.to_string() - } - } -} diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 36e75d5dc9..0af2c1efac 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -25,7 +25,7 @@ use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use gateway_requests::authentication::iv::AuthenticationIV; use gateway_requests::registration::handshake::{client_handshake, SharedKeys, DEFAULT_RNG}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse}; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use nymsphinx::forwarding::packet::MixPacket; use std::convert::TryFrom; use std::sync::Arc; use std::time::Duration; @@ -320,9 +320,9 @@ impl GatewayClient { } } - pub async fn batch_send_sphinx_packets( + pub async fn batch_send_mix_packets( &mut self, - packets: Vec<(NymNodeRoutingAddress, SphinxPacket)>, + packets: Vec, ) -> Result<(), GatewayClientError> { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); @@ -333,8 +333,8 @@ impl GatewayClient { let messages: Vec<_> = packets .into_iter() - .map(|(address, packet)| { - BinaryRequest::new_forward_request(address, packet).into_ws_message( + .map(|mix_packet| { + BinaryRequest::new_forward_request(mix_packet).into_ws_message( self.shared_key .as_ref() .expect("no shared key present even though we're authenticated!"), @@ -347,10 +347,9 @@ impl GatewayClient { } // TODO: possibly make responses optional - pub async fn send_sphinx_packet( + pub async fn send_mix_packet( &mut self, - address: NymNodeRoutingAddress, - packet: SphinxPacket, + mix_packet: MixPacket, ) -> Result<(), GatewayClientError> { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); @@ -360,7 +359,7 @@ impl GatewayClient { } // note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should // be more explicit in the naming? - let msg = BinaryRequest::new_forward_request(address, packet).into_ws_message( + let msg = BinaryRequest::new_forward_request(mix_packet).into_ws_message( self.shared_key .as_ref() .expect("no shared key present even though we're authenticated!"), diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index 1d2dcf9ba6..77ad5b2160 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -183,23 +183,14 @@ pub(crate) enum SocketState { impl SocketState { pub(crate) fn is_available(&self) -> bool { - match self { - SocketState::Available(_) => true, - _ => false, - } + matches!(self, SocketState::Available(_)) } pub(crate) fn is_partially_delegated(&self) -> bool { - match self { - SocketState::PartiallyDelegated(_) => true, - _ => false, - } + matches!(self, SocketState::PartiallyDelegated(_)) } pub(crate) fn is_established(&self) -> bool { - match self { - SocketState::Available(_) | SocketState::PartiallyDelegated(_) => true, - _ => false, - } + matches!(self, SocketState::Available(_) | SocketState::PartiallyDelegated(_)) } } diff --git a/common/client-libs/directory-client/Cargo.toml b/common/client-libs/metrics-client/Cargo.toml similarity index 56% rename from common/client-libs/directory-client/Cargo.toml rename to common/client-libs/metrics-client/Cargo.toml index 090d8fd59b..5bff198dc0 100644 --- a/common/client-libs/directory-client/Cargo.toml +++ b/common/client-libs/metrics-client/Cargo.toml @@ -1,23 +1,15 @@ [package] -name = "directory-client" +name = "metrics-client" version = "0.1.0" -authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] +authors = ["Jedrzej Stuczynski "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -offline-test = [] - [dependencies] -log = "0.4" -pretty_env_logger = "0.3" serde = { version = "1.0", features = ["derive"] } reqwest = { version = "0.10", features = ["json"] } -## internal -directory-client-models = { path = "models" } - [dev-dependencies] mockito = "0.23.0" tokio = { version = "0.2", features = ["macros"] } \ No newline at end of file diff --git a/common/client-libs/metrics-client/src/lib.rs b/common/client-libs/metrics-client/src/lib.rs new file mode 100644 index 0000000000..a38caeb4fb --- /dev/null +++ b/common/client-libs/metrics-client/src/lib.rs @@ -0,0 +1,74 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod models; +pub mod requests; + +use crate::models::metrics::{MixMetric, MixMetricInterval, PersistedMixMetric}; +use crate::requests::metrics_mixes_get::Request as MetricsMixRequest; +use crate::requests::metrics_mixes_post::Request as MetricsMixPost; + +pub struct Config { + pub base_url: String, +} + +impl Config { + pub fn new(base_url: String) -> Self { + Config { base_url } + } +} + +pub struct Client { + base_url: String, + reqwest_client: reqwest::Client, +} + +impl Client { + pub fn new(config: Config) -> Client { + let reqwest_client = reqwest::Client::new(); + Client { + base_url: config.base_url, + reqwest_client, + } + } + + pub async fn post_mix_metrics(&self, metrics: MixMetric) -> reqwest::Result { + let req = MetricsMixPost::new(&self.base_url, metrics); + self.reqwest_client + .post(&req.url()) + .json(req.json_payload()) + .send() + .await? + .json() + .await + } + + pub async fn get_mix_metrics(&self) -> reqwest::Result> { + let req = MetricsMixRequest::new(&self.base_url); + self.reqwest_client + .get(&req.url()) + .send() + .await? + .json() + .await + } +} + +#[cfg(test)] +pub(crate) fn client_test_fixture(base_url: &str) -> Client { + Client { + base_url: base_url.to_string(), + reqwest_client: reqwest::Client::new(), + } +} diff --git a/common/client-libs/directory-client/models/src/metrics.rs b/common/client-libs/metrics-client/src/models/metrics.rs similarity index 88% rename from common/client-libs/directory-client/models/src/metrics.rs rename to common/client-libs/metrics-client/src/models/metrics.rs index 265ae16575..23a4bcd537 100644 --- a/common/client-libs/directory-client/models/src/metrics.rs +++ b/common/client-libs/metrics-client/src/models/metrics.rs @@ -31,3 +31,9 @@ pub struct MixMetric { pub received: u64, pub sent: HashMap, } + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MixMetricInterval { + pub next_report_in: u64, +} diff --git a/common/client-libs/directory-client/models/src/lib.rs b/common/client-libs/metrics-client/src/models/mod.rs similarity index 97% rename from common/client-libs/directory-client/models/src/lib.rs rename to common/client-libs/metrics-client/src/models/mod.rs index c577e00bea..a5e5de4800 100644 --- a/common/client-libs/directory-client/models/src/lib.rs +++ b/common/client-libs/metrics-client/src/models/mod.rs @@ -13,4 +13,3 @@ // limitations under the License. pub mod metrics; -pub mod presence; diff --git a/common/client-libs/directory-client/src/requests/metrics_mixes_get.rs b/common/client-libs/metrics-client/src/requests/metrics_mixes_get.rs similarity index 96% rename from common/client-libs/directory-client/src/requests/metrics_mixes_get.rs rename to common/client-libs/metrics-client/src/requests/metrics_mixes_get.rs index 09b8ade2ce..fdc01c4ccd 100644 --- a/common/client-libs/directory-client/src/requests/metrics_mixes_get.rs +++ b/common/client-libs/metrics-client/src/requests/metrics_mixes_get.rs @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{DirectoryGetRequest, DirectoryRequest}; -use crate::metrics::PersistedMixMetric; - const PATH: &str = "/api/metrics/mixes"; pub struct Request { @@ -22,21 +19,17 @@ pub struct Request { path: String, } -impl DirectoryRequest for Request { - fn url(&self) -> String { - format!("{}{}", self.base_url, self.path) - } -} - -impl DirectoryGetRequest for Request { - type JSONResponse = Vec; - - fn new(base_url: &str) -> Self { +impl Request { + pub(crate) fn new(base_url: &str) -> Self { Request { base_url: base_url.to_string(), path: PATH.to_string(), } } + + pub(crate) fn url(&self) -> String { + format!("{}{}", self.base_url, self.path) + } } #[cfg(test)] diff --git a/common/client-libs/directory-client/src/requests/metrics_mixes_post.rs b/common/client-libs/metrics-client/src/requests/metrics_mixes_post.rs similarity index 73% rename from common/client-libs/directory-client/src/requests/metrics_mixes_post.rs rename to common/client-libs/metrics-client/src/requests/metrics_mixes_post.rs index c6d4600a97..1e38f2d46a 100644 --- a/common/client-libs/directory-client/src/requests/metrics_mixes_post.rs +++ b/common/client-libs/metrics-client/src/requests/metrics_mixes_post.rs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{DirectoryPostRequest, DirectoryRequest}; -use crate::metrics::MixMetric; +use crate::models::metrics::MixMetric; const PATH: &str = "/api/metrics/mixes"; @@ -23,25 +22,22 @@ pub struct Request { payload: MixMetric, } -impl DirectoryRequest for Request { - fn url(&self) -> String { +impl Request { + pub(crate) fn url(&self) -> String { format!("{}{}", self.base_url, self.path) } -} -impl DirectoryPostRequest for Request { - type Payload = MixMetric; - fn json_payload(&self) -> &MixMetric { - &self.payload - } - - fn new(base_url: &str, payload: Self::Payload) -> Self { + pub(crate) fn new(base_url: &str, payload: MixMetric) -> Self { Request { base_url: base_url.to_string(), path: PATH.to_string(), payload, } } + + pub(crate) fn json_payload(&self) -> &MixMetric { + &self.payload + } } #[cfg(test)] @@ -59,7 +55,7 @@ mod metrics_post_request { let _m = mock("POST", PATH).with_status(400).create(); let client = client_test_fixture(&mockito::server_url()); let result = client.post_mix_metrics(fixtures::new_metric()).await; - assert_eq!(400, result.unwrap().status()); + assert!(result.is_err()); _m.assert(); } } @@ -69,7 +65,7 @@ mod metrics_post_request { use super::*; #[tokio::test] async fn it_returns_a_response_with_200() { - let json = fixtures::mix_metrics_response_json(); + let json = fixtures::metrics_interval_json(); let _m = mock("POST", "/api/metrics/mixes") .with_status(201) .with_body(json) @@ -83,7 +79,7 @@ mod metrics_post_request { #[cfg(test)] mod fixtures { - use crate::metrics::MixMetric; + use crate::models::metrics::MixMetric; pub fn new_metric() -> MixMetric { MixMetric { @@ -94,17 +90,11 @@ mod metrics_post_request { } #[cfg(test)] - pub fn mix_metrics_response_json() -> String { + pub fn metrics_interval_json() -> String { r#" - { - "pubKey": "OwOqwWjh_IlnaWS2PxO6odnhNahOYpRCkju50beQCTA=", - "sent": { - "35.178.213.77:1789": 1, - "52.56.99.196:1789": 2 - }, - "received": 10, - "timestamp": 1576061080635800000 - } + { + "nextReportIn": 5 + } "# .to_string() } diff --git a/validator/src/network/ethereum/ethereum.rs b/common/client-libs/metrics-client/src/requests/mod.rs similarity index 91% rename from validator/src/network/ethereum/ethereum.rs rename to common/client-libs/metrics-client/src/requests/mod.rs index 69f4679332..4fe8f6c3d3 100644 --- a/validator/src/network/ethereum/ethereum.rs +++ b/common/client-libs/metrics-client/src/requests/mod.rs @@ -12,4 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -// placeholder for Ethereum / ERC20 bridge integration +pub mod metrics_mixes_get; +pub mod metrics_mixes_post; diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs new file mode 100644 index 0000000000..4944f0c8a1 --- /dev/null +++ b/common/client-libs/mixnet-client/src/client.rs @@ -0,0 +1,156 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::connection_manager::{ConnectionManager, ConnectionManagerSender}; +use futures::channel::oneshot; +use futures::future::AbortHandle; +use log::*; +use nymsphinx::framing::packet::FramedSphinxPacket; +use nymsphinx::params::PacketMode; +use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use std::collections::HashMap; +use std::io; +use std::net::SocketAddr; +use std::time::Duration; + +pub struct Config { + initial_reconnection_backoff: Duration, + maximum_reconnection_backoff: Duration, + initial_connection_timeout: Duration, + maximum_reconnection_attempts: u32, +} + +impl Config { + pub fn new( + initial_reconnection_backoff: Duration, + maximum_reconnection_backoff: Duration, + initial_connection_timeout: Duration, + maximum_reconnection_attempts: u32, + ) -> Self { + Config { + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + maximum_reconnection_attempts, + } + } +} + +pub struct Client { + connections_managers: HashMap, + maximum_reconnection_backoff: Duration, + initial_reconnection_backoff: Duration, + initial_connection_timeout: Duration, + maximum_reconnection_attempts: u32, +} + +impl Client { + pub fn new(config: Config) -> Client { + Client { + connections_managers: HashMap::new(), + initial_reconnection_backoff: config.initial_reconnection_backoff, + maximum_reconnection_backoff: config.maximum_reconnection_backoff, + initial_connection_timeout: config.initial_connection_timeout, + maximum_reconnection_attempts: config.maximum_reconnection_attempts, + } + } + + async fn start_new_connection_manager( + &mut self, + address: SocketAddr, + ) -> Result<(ConnectionManagerSender, AbortHandle), io::Error> { + let conn_manager = ConnectionManager::new( + address, + self.initial_reconnection_backoff, + self.maximum_reconnection_backoff, + self.initial_connection_timeout, + self.maximum_reconnection_attempts, + ) + .await?; + + let (sender, abort_handle) = conn_manager.spawn_abortable(); + + Ok((sender, abort_handle)) + } + + // if wait_for_response is set to true, we will get information about any possible IO errors + // as well as (once implemented) received replies, however, this will also cause way longer + // waiting periods + pub async fn send( + &mut self, + address: NymNodeRoutingAddress, + packet: SphinxPacket, + packet_mode: PacketMode, + wait_for_response: bool, + ) -> io::Result<()> { + trace!("Sending packet to {:?}", address); + let socket_address = address.into(); + + if !self.connections_managers.contains_key(&socket_address) { + debug!( + "There is no existing connection to {:?} - it will be established now", + address + ); + + let (new_manager_sender, abort_handle) = + match self.start_new_connection_manager(socket_address).await { + Ok(res) => res, + Err(err) => { + debug!( + "failed to establish initial connection to {} - {}", + socket_address, err + ); + return Err(err); + } + }; + + self.connections_managers + .insert(socket_address, (new_manager_sender, abort_handle)); + } + + let manager = self.connections_managers.get_mut(&socket_address).unwrap(); + + let framed_packet = FramedSphinxPacket::new(packet, packet_mode); + + let (res_tx, res_rx) = if wait_for_response { + let (res_tx, res_rx) = oneshot::channel(); + (Some(res_tx), Some(res_rx)) + } else { + (None, None) + }; + + if let Err(err) = manager.0.unbounded_send((framed_packet, res_tx)) { + warn!( + "Connection manager to {} has failed - {}", + socket_address, err + ); + self.connections_managers.remove(&socket_address); + return Err(io::Error::new(io::ErrorKind::BrokenPipe, err)); + } + + if let Some(res_rx) = res_rx { + res_rx.await.unwrap() + } else { + Ok(()) + } + } +} + +impl Drop for Client { + fn drop(&mut self) { + for (_, abort_handle) in self.connections_managers.values() { + abort_handle.abort() + } + } +} diff --git a/common/client-libs/mixnet-client/src/connection_manager/mod.rs b/common/client-libs/mixnet-client/src/connection_manager/mod.rs index 107e01de80..f5b87765d5 100644 --- a/common/client-libs/mixnet-client/src/connection_manager/mod.rs +++ b/common/client-libs/mixnet-client/src/connection_manager/mod.rs @@ -19,19 +19,19 @@ use futures::future::{abortable, AbortHandle}; use futures::task::Poll; use futures::{SinkExt, StreamExt}; use log::*; -use nymsphinx::SphinxPacket; +use nymsphinx::framing::packet::FramedSphinxPacket; use std::io; use std::net::SocketAddr; use std::time::Duration; -use tokio::runtime::Handle; mod reconnector; mod writer; pub(crate) type ResponseSender = Option>>; -pub(crate) type ConnectionManagerSender = mpsc::UnboundedSender<(SphinxPacket, ResponseSender)>; -type ConnectionManagerReceiver = mpsc::UnboundedReceiver<(SphinxPacket, ResponseSender)>; +pub(crate) type ConnectionManagerSender = + mpsc::UnboundedSender<(FramedSphinxPacket, ResponseSender)>; +type ConnectionManagerReceiver = mpsc::UnboundedReceiver<(FramedSphinxPacket, ResponseSender)>; enum ConnectionState<'a> { Writing(ConnectionWriter), @@ -46,6 +46,7 @@ pub(crate) struct ConnectionManager<'a> { maximum_reconnection_backoff: Duration, reconnection_backoff: Duration, + maximum_reconnection_attempts: u32, state: ConnectionState<'a>, } @@ -62,59 +63,68 @@ impl<'a> ConnectionManager<'static> { reconnection_backoff: Duration, maximum_reconnection_backoff: Duration, connection_timeout: Duration, - ) -> ConnectionManager<'a> { + maximum_reconnection_attempts: u32, + ) -> Result, io::Error> { let (conn_tx, conn_rx) = mpsc::unbounded(); // the blocking call here is fine as initially we want to wait the timeout interval (at most) anyway: let tcp_stream_res = std::net::TcpStream::connect_timeout(&address, connection_timeout); + // we MUST succeed in making initial connection. We don't want to end up in reconnection + // loop to something we have never managed to connect (and possibly never will) + let initial_state = match tcp_stream_res { Ok(stream) => { let tokio_stream = tokio::net::TcpStream::from_std(stream).unwrap(); debug!("managed to establish initial connection to {}", address); ConnectionState::Writing(ConnectionWriter::new(tokio_stream)) } - Err(e) => { - warn!("failed to establish initial connection to {} within {:?} ({}). Going into reconnection mode", address, connection_timeout, e); - ConnectionState::Reconnecting(ConnectionReconnector::new( - address, - reconnection_backoff, - maximum_reconnection_backoff, - )) - } + Err(err) => return Err(err), }; - ConnectionManager { + Ok(ConnectionManager { conn_tx, conn_rx, address, maximum_reconnection_backoff, reconnection_backoff, + maximum_reconnection_attempts, state: initial_state, - } + }) } async fn run(mut self) { while let Some(msg) = self.conn_rx.next().await { - let (msg_content, res_ch) = msg; - let res = self.handle_new_packet(msg_content).await; - if let Some(res_ch) = res_ch { - if let Err(e) = res_ch.send(res) { - error!( - "failed to send response on the channel to the caller! - {:?}", - e + let (framed_packet, res_ch) = msg; + + match self.handle_new_packet(framed_packet).await { + None => { + warn!( + "We reached maximum number of attempts trying to reconnect to {}", + self.address ); + return; + } + Some(res) => { + if let Some(res_ch) = res_ch { + if let Err(e) = res_ch.send(res) { + error!( + "failed to send response on the channel to the caller! - {:?}", + e + ); + } + } } } } } /// consumes Self and returns channel for communication as well as an `AbortHandle` - pub(crate) fn start_abortable(self, handle: &Handle) -> (ConnectionManagerSender, AbortHandle) { + pub(crate) fn spawn_abortable(self) -> (ConnectionManagerSender, AbortHandle) { let sender_clone = self.conn_tx.clone(); let (abort_fut, abort_handle) = abortable(self.run()); - handle.spawn(async move { abort_fut.await }); + tokio::spawn(async move { abort_fut.await }); (sender_clone, abort_handle) } @@ -122,7 +132,7 @@ impl<'a> ConnectionManager<'static> { // Possible future TODO: `Framed<...>` is both a Sink and a Stream, // so it is possible to read any responses we might receive (it is also duplex, so that could be // done while writing packets themselves). But it'd require slight additions to `SphinxCodec` - async fn handle_new_packet(&mut self, packet: SphinxPacket) -> io::Result<()> { + async fn handle_new_packet(&mut self, packet: FramedSphinxPacket) -> Option> { // we don't do a match here as it's possible to transition from ConnectionState::Reconnecting to ConnectionState::Writing // in this function call. And if that happens, we want to send the packet we have received. if let ConnectionState::Reconnecting(conn_reconnector) = &mut self.state { @@ -130,22 +140,27 @@ impl<'a> ConnectionManager<'static> { let new_connection = match futures::poll(conn_reconnector).await { Poll::Pending => { debug!("The packet is getting dropped - there's nowhere to send it"); - return Err(io::Error::new( + return Some(Err(io::Error::new( io::ErrorKind::BrokenPipe, "connection is broken - reconnection is in progress", - )); + ))); } Poll::Ready(conn) => conn, }; - debug!("Managed to reconnect to {}!", self.address); - self.state = ConnectionState::Writing(ConnectionWriter::new(new_connection)); + match new_connection { + Ok(new_conn) => { + debug!("Managed to reconnect to {}!", self.address); + self.state = ConnectionState::Writing(ConnectionWriter::new(new_conn)); + } + Err(_) => return None, + } } // we must be in writing state if we are here, either by being here from beginning or just // transitioning from reconnecting if let ConnectionState::Writing(conn_writer) = &mut self.state { - if let Err(e) = conn_writer.send(packet).await { + return if let Err(e) = conn_writer.send(packet).await { warn!( "Failed to forward message - {:?}. Starting reconnection procedure...", e @@ -154,14 +169,15 @@ impl<'a> ConnectionManager<'static> { self.address, self.reconnection_backoff, self.maximum_reconnection_backoff, + self.maximum_reconnection_attempts, )); - return Err(io::Error::new( + Some(Err(io::Error::new( io::ErrorKind::BrokenPipe, "connection is broken - reconnection is in progress", - )); + ))) } else { - return Ok(()); - } + Some(Ok(())) + }; } unreachable!(); diff --git a/common/client-libs/mixnet-client/src/connection_manager/reconnector.rs b/common/client-libs/mixnet-client/src/connection_manager/reconnector.rs index 035a5828ac..b57b09f03a 100644 --- a/common/client-libs/mixnet-client/src/connection_manager/reconnector.rs +++ b/common/client-libs/mixnet-client/src/connection_manager/reconnector.rs @@ -22,11 +22,14 @@ use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; +pub(crate) struct MaximumReconnectionCountReached; + pub(crate) struct ConnectionReconnector<'a> { address: SocketAddr, connection: BoxFuture<'a, io::Result>, current_retry_attempt: u32, + maximum_reconnection_attempts: u32, current_backoff_delay: tokio::time::Delay, maximum_reconnection_backoff: Duration, @@ -39,12 +42,14 @@ impl<'a> ConnectionReconnector<'a> { address: SocketAddr, initial_reconnection_backoff: Duration, maximum_reconnection_backoff: Duration, + maximum_reconnection_attempts: u32, ) -> ConnectionReconnector<'a> { ConnectionReconnector { address, connection: tokio::net::TcpStream::connect(address).boxed(), current_backoff_delay: tokio::time::delay_for(Duration::new(0, 0)), // if we can re-establish connection on first try without any backoff that's perfect current_retry_attempt: 0, + maximum_reconnection_attempts, maximum_reconnection_backoff, initial_reconnection_backoff, } @@ -52,7 +57,7 @@ impl<'a> ConnectionReconnector<'a> { } impl<'a> Future for ConnectionReconnector<'a> { - type Output = tokio::net::TcpStream; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // see if we are still in exponential backoff @@ -72,6 +77,11 @@ impl<'a> Future for ConnectionReconnector<'a> { self.address, e, self.current_retry_attempt ); + // checked if we reached the maximum attempt count + if self.current_retry_attempt == self.maximum_reconnection_attempts { + return Poll::Ready(Err(MaximumReconnectionCountReached)); + } + // we failed to re-establish connection - continue exponential backoff // according to https://github.com/tokio-rs/tokio/issues/1953 there's an undocumented @@ -102,7 +112,7 @@ impl<'a> Future for ConnectionReconnector<'a> { Poll::Pending } - Poll::Ready(Ok(conn)) => Poll::Ready(conn), + Poll::Ready(Ok(conn)) => Poll::Ready(Ok(conn)), } } } diff --git a/common/client-libs/mixnet-client/src/connection_manager/writer.rs b/common/client-libs/mixnet-client/src/connection_manager/writer.rs index 1208049b01..b23caec812 100644 --- a/common/client-libs/mixnet-client/src/connection_manager/writer.rs +++ b/common/client-libs/mixnet-client/src/connection_manager/writer.rs @@ -14,8 +14,8 @@ use futures::task::{Context, Poll}; use futures::Sink; -use nymsphinx::framing::{SphinxCodec, SphinxCodecError}; -use nymsphinx::SphinxPacket; +use nymsphinx::framing::codec::{SphinxCodec, SphinxCodecError}; +use nymsphinx::framing::packet::FramedSphinxPacket; use std::pin::Pin; use tokio_util::codec::Framed; @@ -31,14 +31,14 @@ impl ConnectionWriter { } } -impl Sink for ConnectionWriter { +impl Sink for ConnectionWriter { type Error = SphinxCodecError; fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.framed_connection).poll_ready(cx) } - fn start_send(mut self: Pin<&mut Self>, item: SphinxPacket) -> Result<(), Self::Error> { + fn start_send(mut self: Pin<&mut Self>, item: FramedSphinxPacket) -> Result<(), Self::Error> { Pin::new(&mut self.framed_connection).start_send(item) } diff --git a/common/client-libs/mixnet-client/src/forwarder.rs b/common/client-libs/mixnet-client/src/forwarder.rs new file mode 100644 index 0000000000..0dbb3b24f0 --- /dev/null +++ b/common/client-libs/mixnet-client/src/forwarder.rs @@ -0,0 +1,76 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::client::{Client, Config}; +use futures::channel::mpsc; +use futures::StreamExt; +use log::*; +use nymsphinx::forwarding::packet::MixPacket; +use std::time::Duration; + +pub type MixForwardingSender = mpsc::UnboundedSender; +type MixForwardingReceiver = mpsc::UnboundedReceiver; + +/// A specialisation of client such that it forwards any received packets on the channel into the +/// mix network immediately, i.e. will not try to listen for any responses. +pub struct PacketForwarder { + mixnet_client: Client, + packet_receiver: MixForwardingReceiver, +} + +impl PacketForwarder { + pub fn new( + initial_reconnection_backoff: Duration, + maximum_reconnection_backoff: Duration, + initial_connection_timeout: Duration, + maximum_reconnection_attempts: u32, + ) -> (PacketForwarder, MixForwardingSender) { + let client_config = Config::new( + initial_reconnection_backoff, + maximum_reconnection_backoff, + initial_connection_timeout, + maximum_reconnection_attempts, + ); + + let (packet_sender, packet_receiver) = mpsc::unbounded(); + + ( + PacketForwarder { + mixnet_client: Client::new(client_config), + packet_receiver, + }, + packet_sender, + ) + } + + pub async fn run(&mut self) { + while let Some(mix_packet) = self.packet_receiver.next().await { + trace!("Going to forward packet to {:?}", mix_packet.next_hop()); + + let next_hop = mix_packet.next_hop(); + let packet_mode = mix_packet.packet_mode(); + let sphinx_packet = mix_packet.into_sphinx_packet(); + // we don't care about responses, we just want to fire packets + // as quickly as possible + + if let Err(err) = self + .mixnet_client + .send(next_hop, sphinx_packet, packet_mode, false) + .await + { + debug!("failed to forward the packet - {}", err) + } + } + } +} diff --git a/common/client-libs/mixnet-client/src/lib.rs b/common/client-libs/mixnet-client/src/lib.rs index d4c72240b0..cb66dadb90 100644 --- a/common/client-libs/mixnet-client/src/lib.rs +++ b/common/client-libs/mixnet-client/src/lib.rs @@ -12,272 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::connection_manager::{ConnectionManager, ConnectionManagerSender}; -use futures::channel::oneshot; -use futures::future::AbortHandle; -use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::collections::HashMap; -use std::io; -use std::net::SocketAddr; -use std::time::Duration; -use tokio::runtime::Handle; - -mod connection_manager; - -pub struct Config { - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, -} - -impl Config { - pub fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, - ) -> Self { - Config { - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - } - } -} - -pub struct Client { - runtime_handle: Handle, - connections_managers: HashMap, - maximum_reconnection_backoff: Duration, - initial_reconnection_backoff: Duration, - initial_connection_timeout: Duration, -} - -impl Client { - pub fn new(config: Config) -> Client { - Client { - // if the function is not called within tokio runtime context, this will panic - // but perhaps the code should be better structured to completely avoid this call - runtime_handle: Handle::try_current() - .expect("The client MUST BE used within tokio runtime context"), - connections_managers: HashMap::new(), - initial_reconnection_backoff: config.initial_reconnection_backoff, - maximum_reconnection_backoff: config.maximum_reconnection_backoff, - initial_connection_timeout: config.initial_connection_timeout, - } - } - - async fn start_new_connection_manager( - &mut self, - address: SocketAddr, - ) -> (ConnectionManagerSender, AbortHandle) { - let (sender, abort_handle) = ConnectionManager::new( - address, - self.initial_reconnection_backoff, - self.maximum_reconnection_backoff, - self.initial_connection_timeout, - ) - .await - .start_abortable(&self.runtime_handle); - - (sender, abort_handle) - } - - // if wait_for_response is set to true, we will get information about any possible IO errors - // as well as (once implemented) received replies, however, this will also cause way longer - // waiting periods - pub async fn send( - &mut self, - address: NymNodeRoutingAddress, - packet: SphinxPacket, - wait_for_response: bool, - ) -> io::Result<()> { - trace!("Sending packet to {:?}", address); - let socket_address = address.into(); - - if !self.connections_managers.contains_key(&socket_address) { - debug!( - "There is no existing connection to {:?} - it will be established now", - address - ); - - let (new_manager_sender, abort_handle) = - self.start_new_connection_manager(socket_address).await; - self.connections_managers - .insert(socket_address, (new_manager_sender, abort_handle)); - } - - let manager = self.connections_managers.get_mut(&socket_address).unwrap(); - - if wait_for_response { - let (res_tx, res_rx) = oneshot::channel(); - manager.0.unbounded_send((packet, Some(res_tx))).unwrap(); - res_rx.await.unwrap() - } else { - manager.0.unbounded_send((packet, None)).unwrap(); - Ok(()) - } - } -} - -impl Drop for Client { - fn drop(&mut self) { - for (_, abort_handle) in self.connections_managers.values() { - abort_handle.abort() - } - } -} - -/* - The below tests weren't extremely reliable to begin with, however, - to restore them as they were before, we'd need to expose some kind of 'SphinxPacket::test_fixture()` - function. -*/ -// -// #[cfg(test)] -// mod tests { -// use super::*; -// use std::str; -// use std::time; -// use tokio::prelude::*; -// -// const SERVER_MSG_LEN: usize = 16; -// const CLOSE_MESSAGE: [u8; SERVER_MSG_LEN] = [0; SERVER_MSG_LEN]; -// -// struct DummyServer { -// received_buf: Vec>, -// listener: tokio::net::TcpListener, -// } -// -// impl DummyServer { -// async fn new(address: SocketAddr) -> Self { -// DummyServer { -// received_buf: Vec::new(), -// listener: tokio::net::TcpListener::bind(address).await.unwrap(), -// } -// } -// -// fn get_received(&self) -> Vec> { -// self.received_buf.clone() -// } -// -// // this is only used in tests so slightly higher logging levels are fine -// async fn listen_until(mut self, close_message: &[u8]) -> Self { -// let (mut socket, _) = self.listener.accept().await.unwrap(); -// loop { -// let mut buf = [0u8; SERVER_MSG_LEN]; -// match socket.read(&mut buf).await { -// Ok(n) if n == 0 => { -// info!("Remote connection closed"); -// return self; -// } -// Ok(n) => { -// info!("received ({}) - {:?}", n, str::from_utf8(buf[..n].as_ref())); -// -// if buf[..n].as_ref() == close_message { -// info!("closing..."); -// socket.shutdown(std::net::Shutdown::Both).unwrap(); -// return self; -// } else { -// self.received_buf.push(buf[..n].to_vec()); -// } -// } -// Err(e) => { -// panic!("failed to read from socket; err = {:?}", e); -// } -// }; -// } -// } -// } -// -// #[test] -// fn client_reconnects_to_server_after_it_went_down() { -// let mut rt = tokio::runtime::Runtime::new().unwrap(); -// let addr = "127.0.0.1:6000".parse().unwrap(); -// let reconnection_backoff = Duration::from_secs(1); -// let timeout = Duration::from_secs(1); -// let client_config = Config::new(reconnection_backoff, 10 * reconnection_backoff, timeout); -// -// let messages_to_send = vec![[1u8; SERVER_MSG_LEN].to_vec(), [2; SERVER_MSG_LEN].to_vec()]; -// -// let dummy_server = rt.block_on(DummyServer::new(addr)); -// let finished_dummy_server_future = rt.spawn(dummy_server.listen_until(&CLOSE_MESSAGE)); -// -// let mut c = rt.enter(|| Client::new(client_config)); -// -// for msg in &messages_to_send { -// rt.block_on(c.send(addr, msg.clone(), true)).unwrap(); -// } -// -// // kill server -// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true)) -// .unwrap(); -// let received_messages = rt -// .block_on(finished_dummy_server_future) -// .unwrap() -// .get_received(); -// -// assert_eq!(received_messages, messages_to_send); -// -// // try to send - go into reconnection -// let post_kill_message = [3u8; SERVER_MSG_LEN].to_vec(); -// -// // we are trying to send to killed server -// assert!(rt -// .block_on(c.send(addr, post_kill_message.clone(), true)) -// .is_err()); -// -// let new_dummy_server = rt.block_on(DummyServer::new(addr)); -// let new_server_future = rt.spawn(new_dummy_server.listen_until(&CLOSE_MESSAGE)); -// -// // keep sending after we leave reconnection backoff and reconnect -// loop { -// if rt -// .block_on(c.send(addr, post_kill_message.clone(), true)) -// .is_ok() -// { -// break; -// } -// rt.block_on( -// async move { tokio::time::delay_for(time::Duration::from_millis(50)).await }, -// ); -// } -// -// // kill the server to ensure it actually got the message -// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true)) -// .unwrap(); -// let new_received_messages = rt.block_on(new_server_future).unwrap().get_received(); -// assert_eq!(post_kill_message.to_vec(), new_received_messages[0]); -// } -// -// #[test] -// fn server_receives_all_sent_messages_when_up() { -// let mut rt = tokio::runtime::Runtime::new().unwrap(); -// let addr = "127.0.0.1:6001".parse().unwrap(); -// let reconnection_backoff = Duration::from_secs(2); -// let timeout = Duration::from_secs(1); -// let client_config = Config::new(reconnection_backoff, 10 * reconnection_backoff, timeout); -// -// let messages_to_send = vec![[1u8; SERVER_MSG_LEN].to_vec(), [2; SERVER_MSG_LEN].to_vec()]; -// -// let dummy_server = rt.block_on(DummyServer::new(addr)); -// let finished_dummy_server_future = rt.spawn(dummy_server.listen_until(&CLOSE_MESSAGE)); -// -// let mut c = rt.enter(|| Client::new(client_config)); -// -// for msg in &messages_to_send { -// rt.block_on(c.send(addr, msg.clone(), true)).unwrap(); -// } -// -// rt.block_on(c.send(addr, CLOSE_MESSAGE.to_vec(), true)) -// .unwrap(); -// -// // the server future should have already been resolved -// let received_messages = rt -// .block_on(finished_dummy_server_future) -// .unwrap() -// .get_received(); -// -// assert_eq!(received_messages, messages_to_send); -// } -// } +pub mod client; +pub(crate) mod connection_manager; +pub mod forwarder; diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index d9e12aefc7..de75f18f28 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -8,4 +8,12 @@ edition = "2018" [dependencies] log = "0.4" -pretty_env_logger = "0.3" \ No newline at end of file +serde = { version = "1.0", features = ["derive"] } +reqwest = { version = "0.10", features = ["json"] } + +crypto = { path = "../../crypto" } +topology = { path = "../../topology" } + +[dev-dependencies] +mockito = "0.23.0" +tokio = { version = "0.2", features = ["macros"] } \ No newline at end of file diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index 36f90bbc64..fc2c8b8a8b 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -12,10 +12,242 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +use crate::models::gateway::GatewayRegistrationInfo; +use crate::models::mixmining::{BatchMixStatus, MixStatus}; +use crate::models::mixnode::MixRegistrationInfo; +use crate::models::topology::Topology; +use crate::rest_requests::{ + ActiveTopologyGet, ActiveTopologyGetResponse, BatchMixStatusPost, GatewayRegisterPost, + MixRegisterPost, MixStatusPost, NodeUnregisterDelete, RESTRequest, RESTRequestError, + ReputationPatch, TopologyGet, TopologyGetResponse, +}; +use serde::Deserialize; + +pub mod models; +pub mod rest_requests; + +// for ease of use +type Result = std::result::Result; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ErrorResponse { + error: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct OkResponse { + ok: bool, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub(crate) enum DefaultRESTResponse { + Ok(OkResponse), + Error(ErrorResponse), +} + +#[derive(Debug)] +pub enum ValidatorClientError { + RESTRequestError(RESTRequestError), + ReqwestClientError(reqwest::Error), + ValidatorError(String), +} + +impl From for ValidatorClientError { + fn from(err: RESTRequestError) -> Self { + ValidatorClientError::RESTRequestError(err) + } +} + +impl From for ValidatorClientError { + fn from(err: reqwest::Error) -> Self { + ValidatorClientError::ReqwestClientError(err) + } +} + +pub struct Config { + base_url: String, +} + +impl Config { + pub fn new>(base_url: S) -> Self { + Config { + base_url: base_url.into(), + } + } +} + +pub struct Client { + config: Config, + reqwest_client: reqwest::Client, +} + +impl Client { + pub fn new(config: Config) -> Self { + let reqwest_client = reqwest::Client::new(); + Client { + config, + reqwest_client, + } + } + + async fn make_rest_request( + &self, + request: R, + ) -> Result { + let mut req_builder = self + .reqwest_client + .request(R::METHOD, request.url().clone()); + + if let Some(json_payload) = request.json_payload() { + // if applicable, attach payload + req_builder = req_builder.json(json_payload) + } + Ok(req_builder.send().await?.json().await?) + } + + pub async fn register_mix(&self, mix_registration_info: MixRegistrationInfo) -> Result<()> { + let req = MixRegisterPost::new( + &self.config.base_url, + None, + None, + Some(mix_registration_info), + )?; + match self.make_rest_request(req).await? { + DefaultRESTResponse::Ok(_) => Ok(()), + DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + } + } + + pub async fn register_gateway( + &self, + gateway_registration_info: GatewayRegistrationInfo, + ) -> Result<()> { + let req = GatewayRegisterPost::new( + &self.config.base_url, + None, + None, + Some(gateway_registration_info), + )?; + match self.make_rest_request(req).await? { + DefaultRESTResponse::Ok(ok_res) => { + if ok_res.ok { + Ok(()) + } else { + Err(ValidatorClientError::ValidatorError( + "received ok response with false".into(), + )) + } + } + DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + } + } + + pub async fn unregister_node(&self, node_id: &str) -> Result<()> { + let req = + NodeUnregisterDelete::new(&self.config.base_url, Some(vec![node_id]), None, None)?; + + match self.make_rest_request(req).await? { + DefaultRESTResponse::Ok(ok_res) => { + if ok_res.ok { + Ok(()) + } else { + Err(ValidatorClientError::ValidatorError( + "received ok response with false".into(), + )) + } + } + DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + } + } + + pub async fn set_reputation(&self, node_id: &str, new_reputation: i64) -> Result<()> { + let new_rep_string = new_reputation.to_string(); + let query_param_values = vec![&*new_rep_string]; + let query_param_keys = ReputationPatch::query_param_keys(); + + let query_params = query_param_keys + .into_iter() + .zip(query_param_values.into_iter()) + .collect(); + + let req = ReputationPatch::new( + &self.config.base_url, + Some(vec![node_id]), + Some(query_params), + None, + )?; + match self.make_rest_request(req).await? { + DefaultRESTResponse::Ok(ok_res) => { + if ok_res.ok { + Ok(()) + } else { + Err(ValidatorClientError::ValidatorError( + "received ok response with false".into(), + )) + } + } + DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + } + } + + pub async fn get_topology(&self) -> Result { + let req = TopologyGet::new(&self.config.base_url, None, None, None)?; + match self.make_rest_request(req).await? { + TopologyGetResponse::Ok(topology) => Ok(topology), + TopologyGetResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + } + } + + pub async fn get_active_topology(&self) -> Result { + let req = ActiveTopologyGet::new(&self.config.base_url, None, None, None)?; + match self.make_rest_request(req).await? { + ActiveTopologyGetResponse::Ok(topology) => Ok(topology), + ActiveTopologyGetResponse::Error(err) => { + Err(ValidatorClientError::ValidatorError(err.error)) + } + } + } + + pub async fn post_mixmining_status(&self, status: MixStatus) -> Result<()> { + let req = MixStatusPost::new(&self.config.base_url, None, None, Some(status))?; + match self.make_rest_request(req).await? { + DefaultRESTResponse::Ok(ok_res) => { + if ok_res.ok { + Ok(()) + } else { + Err(ValidatorClientError::ValidatorError( + "received ok response with false".into(), + )) + } + } + DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + } + } + + pub async fn post_batch_mixmining_status(&self, batch_status: BatchMixStatus) -> Result<()> { + let req = BatchMixStatusPost::new(&self.config.base_url, None, None, Some(batch_status))?; + match self.make_rest_request(req).await? { + DefaultRESTResponse::Ok(ok_res) => { + if ok_res.ok { + Ok(()) + } else { + Err(ValidatorClientError::ValidatorError( + "received ok response with false".into(), + )) + } + } + DefaultRESTResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + } + } +} + +#[cfg(test)] +pub(crate) fn client_test_fixture(base_url: &str) -> Client { + Client { + config: Config::new(base_url), + reqwest_client: reqwest::Client::new(), } } diff --git a/common/client-libs/validator-client/src/models/gateway.rs b/common/client-libs/validator-client/src/models/gateway.rs new file mode 100644 index 0000000000..c408cb4deb --- /dev/null +++ b/common/client-libs/validator-client/src/models/gateway.rs @@ -0,0 +1,131 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::models::node::NodeInfo; +use crypto::asymmetric::{encryption, identity}; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::io; +use std::net::ToSocketAddrs; + +#[derive(Debug)] +pub enum ConversionError { + InvalidIdentityKeyError(identity::KeyRecoveryError), + InvalidSphinxKeyError(encryption::KeyRecoveryError), + InvalidAddress(io::Error), +} + +impl From for ConversionError { + fn from(err: encryption::KeyRecoveryError) -> Self { + ConversionError::InvalidSphinxKeyError(err) + } +} + +impl From for ConversionError { + fn from(err: identity::KeyRecoveryError) -> Self { + ConversionError::InvalidIdentityKeyError(err) + } +} + +// used for gateways to register themselves +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GatewayRegistrationInfo { + #[serde(flatten)] + pub(crate) node_info: NodeInfo, + pub(crate) clients_host: String, +} + +impl GatewayRegistrationInfo { + pub fn new( + mix_host: String, + clients_host: String, + identity_key: String, + sphinx_key: String, + version: String, + location: String, + incentives_address: Option, + ) -> Self { + GatewayRegistrationInfo { + node_info: NodeInfo { + mix_host, + identity_key, + sphinx_key, + version, + location, + incentives_address: incentives_address.unwrap_or_else(|| "".to_string()), + }, + clients_host, + } + } +} + +// actual entry in topology +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RegisteredGateway { + #[serde(flatten)] + pub(crate) gateway_info: GatewayRegistrationInfo, + pub(crate) registration_time: i64, + pub(crate) reputation: i64, +} + +impl RegisteredGateway { + pub fn identity(&self) -> String { + self.gateway_info.node_info.identity_key.clone() + } + + pub fn mixnet_listener(&self) -> String { + self.gateway_info.node_info.mix_host.clone() + } + + pub fn clients_listener(&self) -> String { + self.gateway_info.clients_host.clone() + } +} + +impl TryInto for RegisteredGateway { + type Error = ConversionError; + + fn try_into(self) -> Result { + let resolved_mix_hostname = self + .gateway_info + .node_info + .mix_host + .to_socket_addrs() + .map_err(ConversionError::InvalidAddress)? + .next() + .ok_or_else(|| { + ConversionError::InvalidAddress(io::Error::new( + io::ErrorKind::Other, + "no valid socket address", + )) + })?; + + Ok(topology::gateway::Node { + location: self.gateway_info.node_info.location, + mixnet_listener: resolved_mix_hostname, + client_listener: self.gateway_info.clients_host, + identity_key: identity::PublicKey::from_base58_string( + self.gateway_info.node_info.identity_key, + )?, + sphinx_key: encryption::PublicKey::from_base58_string( + self.gateway_info.node_info.sphinx_key, + )?, + registration_time: self.registration_time, + reputation: self.reputation, + version: self.gateway_info.node_info.version, + }) + } +} diff --git a/common/client-libs/validator-client/src/models/mixmining.rs b/common/client-libs/validator-client/src/models/mixmining.rs new file mode 100644 index 0000000000..270d1bb7fd --- /dev/null +++ b/common/client-libs/validator-client/src/models/mixmining.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +/// A notification sent to the validators to let them know whether a given mix is +/// currently up or down (based on whether it's mixing packets) +pub struct MixStatus { + pub pub_key: String, + pub ip_version: String, + pub up: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +/// A notification sent to the validators to let them know whether a given set of mixes is +/// currently up or down (based on whether it's mixing packets) +pub struct BatchMixStatus { + pub status: Vec, +} diff --git a/common/client-libs/validator-client/src/models/mixnode.rs b/common/client-libs/validator-client/src/models/mixnode.rs new file mode 100644 index 0000000000..b72eabafe2 --- /dev/null +++ b/common/client-libs/validator-client/src/models/mixnode.rs @@ -0,0 +1,127 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::models::node::NodeInfo; +use crypto::asymmetric::{encryption, identity}; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::io; +use std::net::ToSocketAddrs; + +#[derive(Debug)] +pub enum ConversionError { + InvalidIdentityKeyError(identity::KeyRecoveryError), + InvalidSphinxKeyError(encryption::KeyRecoveryError), + InvalidAddress(io::Error), +} + +impl From for ConversionError { + fn from(err: encryption::KeyRecoveryError) -> Self { + ConversionError::InvalidSphinxKeyError(err) + } +} + +impl From for ConversionError { + fn from(err: identity::KeyRecoveryError) -> Self { + ConversionError::InvalidIdentityKeyError(err) + } +} + +// used for mixnode to register themselves +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MixRegistrationInfo { + #[serde(flatten)] + pub(crate) node_info: NodeInfo, + pub(crate) layer: u64, +} + +impl MixRegistrationInfo { + pub fn new( + mix_host: String, + identity_key: String, + sphinx_key: String, + version: String, + location: String, + layer: u64, + incentives_address: Option, + ) -> Self { + MixRegistrationInfo { + node_info: NodeInfo { + mix_host, + identity_key, + sphinx_key, + version, + location, + incentives_address: incentives_address.unwrap_or_else(|| "".to_string()), + }, + layer, + } + } +} + +// actual entry in topology +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RegisteredMix { + #[serde(flatten)] + pub(crate) mix_info: MixRegistrationInfo, + pub(crate) registration_time: i64, + pub(crate) reputation: i64, +} + +impl RegisteredMix { + pub fn identity(&self) -> String { + self.mix_info.node_info.identity_key.clone() + } + + pub fn mix_host(&self) -> String { + self.mix_info.node_info.mix_host.clone() + } +} + +impl TryInto for RegisteredMix { + type Error = ConversionError; + + fn try_into(self) -> Result { + let resolved_hostname = self + .mix_info + .node_info + .mix_host + .to_socket_addrs() + .map_err(ConversionError::InvalidAddress)? + .next() + .ok_or_else(|| { + ConversionError::InvalidAddress(io::Error::new( + io::ErrorKind::Other, + "no valid socket address", + )) + })?; + + Ok(topology::mix::Node { + location: self.mix_info.node_info.location, + host: resolved_hostname, + identity_key: identity::PublicKey::from_base58_string( + self.mix_info.node_info.identity_key, + )?, + sphinx_key: encryption::PublicKey::from_base58_string( + self.mix_info.node_info.sphinx_key, + )?, + layer: self.mix_info.layer, + registration_time: self.registration_time, + reputation: self.reputation, + version: self.mix_info.node_info.version, + }) + } +} diff --git a/validator/src/network/rest/routes/mod.rs b/common/client-libs/validator-client/src/models/mod.rs similarity index 88% rename from validator/src/network/rest/routes/mod.rs rename to common/client-libs/validator-client/src/models/mod.rs index fcd639a99f..f6076854b1 100644 --- a/validator/src/network/rest/routes/mod.rs +++ b/common/client-libs/validator-client/src/models/mod.rs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use iron::prelude::*; -use iron::status; - +pub mod gateway; +pub mod mixmining; +pub mod mixnode; +mod node; pub mod topology; +pub mod validators; diff --git a/clients/client-core/build.rs b/common/client-libs/validator-client/src/models/node.rs similarity index 61% rename from clients/client-core/build.rs rename to common/client-libs/validator-client/src/models/node.rs index 56d753472c..cfbdd82546 100644 --- a/clients/client-core/build.rs +++ b/common/client-libs/validator-client/src/models/node.rs @@ -12,8 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use built; +use serde::{Deserialize, Serialize}; -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct NodeInfo { + pub(crate) mix_host: String, + pub(crate) identity_key: String, + pub(crate) sphinx_key: String, + pub(crate) version: String, + pub(crate) location: String, + pub(crate) incentives_address: String, } diff --git a/common/client-libs/validator-client/src/models/topology.rs b/common/client-libs/validator-client/src/models/topology.rs new file mode 100644 index 0000000000..5b9eff2a76 --- /dev/null +++ b/common/client-libs/validator-client/src/models/topology.rs @@ -0,0 +1,74 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::models::gateway::RegisteredGateway; +use crate::models::mixnode::RegisteredMix; +use crate::models::validators::ValidatorsOutput; +use log::*; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use topology::{MixLayer, NymTopology}; + +// Topology shows us the current state of the overall Nym network +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Topology { + pub mix_nodes: Vec, + pub gateways: Vec, + pub validators: ValidatorsOutput, +} + +// changed from `TryInto`. reason being is that we should not fail entire topology +// conversion if there's one invalid node on the network screwing around +impl Into for Topology { + fn into(self) -> NymTopology { + use std::collections::HashMap; + + let mut mixes = HashMap::new(); + for mix in self.mix_nodes.into_iter() { + let layer = mix.mix_info.layer as MixLayer; + if layer == 0 || layer > 3 { + warn!( + "{} says it's on invalid layer {}!", + mix.mix_info.node_info.identity_key, layer + ); + continue; + } + let mix_id = mix.mix_info.node_info.identity_key.clone(); + + let layer_entry = mixes.entry(layer).or_insert_with(Vec::new); + match mix.try_into() { + Ok(mix) => layer_entry.push(mix), + Err(err) => { + warn!("Mix {} is malformed - {:?}", mix_id, err); + continue; + } + } + } + + let mut gateways = Vec::with_capacity(self.gateways.len()); + for gate in self.gateways.into_iter() { + let gate_id = gate.gateway_info.node_info.identity_key.clone(); + match gate.try_into() { + Ok(gate) => gateways.push(gate), + Err(err) => { + warn!("Gateway {} is malformed - {:?}", gate_id, err); + continue; + } + } + } + + NymTopology::new(mixes, gateways) + } +} diff --git a/common/client-libs/directory-client/models/src/presence/providers.rs b/common/client-libs/validator-client/src/models/validators.rs similarity index 66% rename from common/client-libs/directory-client/models/src/presence/providers.rs rename to common/client-libs/validator-client/src/models/validators.rs index a4875a048a..511ace0d19 100644 --- a/common/client-libs/directory-client/models/src/presence/providers.rs +++ b/common/client-libs/validator-client/src/models/validators.rs @@ -15,19 +15,15 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MixProviderPresence { - pub location: String, - pub client_listener: String, - pub mixnet_listener: String, - pub pub_key: String, - pub registered_clients: Vec, - pub last_seen: u64, - pub version: String, +pub struct ValidatorsOutput { + pub(crate) block_height: i64, + pub(crate) validators: Vec, } #[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MixProviderClient { - pub pub_key: String, +pub(crate) struct ValidatorOutput { + pub(crate) address: String, + pub(crate) pub_key: String, + pub(crate) proposer_priority: i64, + pub(crate) voting_power: i64, } diff --git a/common/client-libs/validator-client/src/rest_requests/active_topology_get.rs b/common/client-libs/validator-client/src/rest_requests/active_topology_get.rs new file mode 100644 index 0000000000..1973cea1d8 --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/active_topology_get.rs @@ -0,0 +1,54 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::models::topology::Topology; +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::ErrorResponse; +use reqwest::{Method, Url}; +use serde::Deserialize; + +pub struct Request { + url: Url, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub(crate) enum Response { + Ok(Topology), + Error(ErrorResponse), +} + +impl RESTRequest for Request { + const METHOD: Method = Method::GET; + const RELATIVE_PATH: &'static str = "/api/mixmining/topology/active"; + + type JsonPayload = (); + type ExpectedJsonResponse = Response; + + fn new( + base_url: &str, + _: Option>, + _: Option>, + _: Option, + ) -> Result { + let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) + .map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + + Ok(Request { url }) + } + + fn url(&self) -> &Url { + &self.url + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/gateway_register_post.rs b/common/client-libs/validator-client/src/rest_requests/gateway_register_post.rs new file mode 100644 index 0000000000..a2ba865bee --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/gateway_register_post.rs @@ -0,0 +1,52 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::models::gateway::GatewayRegistrationInfo; +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::DefaultRESTResponse; +use reqwest::{Method, Url}; + +pub struct Request { + url: Url, + payload: GatewayRegistrationInfo, +} + +impl RESTRequest for Request { + const METHOD: Method = Method::POST; + const RELATIVE_PATH: &'static str = "/api/mixmining/register/gateway"; + + type JsonPayload = GatewayRegistrationInfo; + type ExpectedJsonResponse = DefaultRESTResponse; + + fn new( + base_url: &str, + _: Option>, + _: Option>, + body_payload: Option, + ) -> Result { + let payload = body_payload.ok_or_else(|| RESTRequestError::NoPayloadProvided)?; + let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) + .map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + + Ok(Request { url, payload }) + } + + fn url(&self) -> &Url { + &self.url + } + + fn json_payload(&self) -> Option<&Self::JsonPayload> { + Some(&self.payload) + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/mix_mining_batch_status_post.rs b/common/client-libs/validator-client/src/rest_requests/mix_mining_batch_status_post.rs new file mode 100644 index 0000000000..df109f9bf1 --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/mix_mining_batch_status_post.rs @@ -0,0 +1,98 @@ +use crate::models::mixmining::BatchMixStatus; +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::DefaultRESTResponse; +use reqwest::{Method, Url}; + +pub struct Request { + url: Url, + payload: BatchMixStatus, +} + +impl RESTRequest for Request { + const METHOD: Method = Method::POST; + const RELATIVE_PATH: &'static str = "/api/mixmining/batch"; + type JsonPayload = BatchMixStatus; + type ExpectedJsonResponse = DefaultRESTResponse; + + fn new( + base_url: &str, + _: Option>, + _: Option>, + body_payload: Option, + ) -> Result { + let payload = body_payload.ok_or_else(|| RESTRequestError::NoPayloadProvided)?; + let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) + .map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + Ok(Request { url, payload }) + } + + fn url(&self) -> &Url { + &self.url + } + + fn json_payload(&self) -> Option<&Self::JsonPayload> { + Some(&self.payload) + } +} + +#[cfg(test)] +mod batch_mix_status_post_request { + use super::*; + use crate::client_test_fixture; + use mockito::mock; + + #[cfg(test)] + mod on_a_400_status { + use super::*; + + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("POST", Request::RELATIVE_PATH) + .with_status(400) + .create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client + .post_batch_mixmining_status(fixtures::new_status()) + .await; + assert!(result.is_err()); + _m.assert(); + } + } + + #[cfg(test)] + mod on_a_201 { + use super::*; + + #[tokio::test] + async fn it_returns_a_response_with_201() { + let json = r#"{ + "ok": true + }"#; + let _m = mock("POST", "/api/mixmining/batch") + .with_status(201) + .with_body(json) + .create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client + .post_batch_mixmining_status(fixtures::new_status()) + .await; + assert!(result.is_ok()); + _m.assert(); + } + } + + #[cfg(test)] + mod fixtures { + use crate::models::mixmining::{BatchMixStatus, MixStatus}; + + pub fn new_status() -> BatchMixStatus { + BatchMixStatus { + status: vec![MixStatus { + pub_key: "abc".to_string(), + ip_version: "4".to_string(), + up: true, + }], + } + } + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/mix_mining_status_post.rs b/common/client-libs/validator-client/src/rest_requests/mix_mining_status_post.rs new file mode 100644 index 0000000000..2f82642d29 --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/mix_mining_status_post.rs @@ -0,0 +1,91 @@ +use crate::models::mixmining::MixStatus; +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::DefaultRESTResponse; +use reqwest::{Method, Url}; + +pub struct Request { + url: Url, + payload: MixStatus, +} + +impl RESTRequest for Request { + const METHOD: Method = Method::POST; + const RELATIVE_PATH: &'static str = "/api/mixmining"; + type JsonPayload = MixStatus; + type ExpectedJsonResponse = DefaultRESTResponse; + + fn new( + base_url: &str, + _: Option>, + _: Option>, + body_payload: Option, + ) -> Result { + let payload = body_payload.ok_or_else(|| RESTRequestError::NoPayloadProvided)?; + let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) + .map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + Ok(Request { url, payload }) + } + + fn url(&self) -> &Url { + &self.url + } + + fn json_payload(&self) -> Option<&Self::JsonPayload> { + Some(&self.payload) + } +} + +#[cfg(test)] +mod mix_status_post_request { + use super::*; + use crate::client_test_fixture; + use mockito::mock; + + #[cfg(test)] + mod on_a_400_status { + use super::*; + + #[tokio::test] + async fn it_returns_an_error() { + let _m = mock("POST", Request::RELATIVE_PATH) + .with_status(400) + .create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client.post_mixmining_status(fixtures::new_status()).await; + assert!(result.is_err()); + _m.assert(); + } + } + + #[cfg(test)] + mod on_a_201 { + use super::*; + #[tokio::test] + async fn it_returns_a_response_with_201() { + let json = r#"{ + "ok": true + }"#; + let _m = mock("POST", Request::RELATIVE_PATH) + .with_status(201) + .with_body(json) + .create(); + let client = client_test_fixture(&mockito::server_url()); + let result = client.post_mixmining_status(fixtures::new_status()).await; + assert!(result.is_ok()); + _m.assert(); + } + } + + #[cfg(test)] + mod fixtures { + use crate::models::mixmining::MixStatus; + + pub fn new_status() -> MixStatus { + MixStatus { + pub_key: "abc".to_string(), + ip_version: "4".to_string(), + up: true, + } + } + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/mix_register_post.rs b/common/client-libs/validator-client/src/rest_requests/mix_register_post.rs new file mode 100644 index 0000000000..4a0c477a1a --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/mix_register_post.rs @@ -0,0 +1,52 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::models::mixnode::MixRegistrationInfo; +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::DefaultRESTResponse; +use reqwest::{Method, Url}; + +pub struct Request { + url: Url, + payload: MixRegistrationInfo, +} + +impl RESTRequest for Request { + const METHOD: Method = Method::POST; + const RELATIVE_PATH: &'static str = "/api/mixmining/register/mix"; + + type JsonPayload = MixRegistrationInfo; + type ExpectedJsonResponse = DefaultRESTResponse; + + fn new( + base_url: &str, + _: Option>, + _: Option>, + body_payload: Option, + ) -> Result { + let payload = body_payload.ok_or_else(|| RESTRequestError::NoPayloadProvided)?; + let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) + .map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + + Ok(Request { url, payload }) + } + + fn url(&self) -> &Url { + &self.url + } + + fn json_payload(&self) -> Option<&Self::JsonPayload> { + Some(&self.payload) + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/mod.rs b/common/client-libs/validator-client/src/rest_requests/mod.rs new file mode 100644 index 0000000000..49b5f54390 --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/mod.rs @@ -0,0 +1,74 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use reqwest::{Method, Url}; +use serde::{de::DeserializeOwned, Serialize}; + +pub(crate) use active_topology_get::{ + Request as ActiveTopologyGet, Response as ActiveTopologyGetResponse, +}; +pub(crate) use gateway_register_post::Request as GatewayRegisterPost; +pub(crate) use mix_mining_batch_status_post::Request as BatchMixStatusPost; +pub(crate) use mix_mining_status_post::Request as MixStatusPost; +pub(crate) use mix_register_post::Request as MixRegisterPost; +pub(crate) use node_unregister_delete::Request as NodeUnregisterDelete; +pub(crate) use set_reputation_patch::Request as ReputationPatch; +pub(crate) use topology_get::{Request as TopologyGet, Response as TopologyGetResponse}; + +pub mod active_topology_get; +pub mod gateway_register_post; +pub mod mix_mining_batch_status_post; +pub mod mix_mining_status_post; +pub mod mix_register_post; +pub mod node_unregister_delete; +pub mod set_reputation_patch; +pub mod topology_get; + +type PathParam<'a> = &'a str; +type QueryParam<'a> = (&'a str, &'a str); + +#[derive(Debug)] +pub enum RESTRequestError { + InvalidPathParams, + InvalidQueryParams, + NoPayloadProvided, + MalformedUrl(String), +} + +pub(crate) trait RESTRequest { + const METHOD: Method; // 'GET', 'POST', 'DELETE', etc. + const RELATIVE_PATH: &'static str; + + type JsonPayload: Serialize + Sized; + type ExpectedJsonResponse: DeserializeOwned + Sized; + + fn new( + base_url: &str, + path_params: Option>, + query_params: Option>, + body_payload: Option, + ) -> Result + where + Self: Sized; + + fn url(&self) -> &Url; + + fn json_payload(&self) -> Option<&Self::JsonPayload> { + None + } + + fn query_param_keys() -> Vec<&'static str> { + Vec::new() + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/node_unregister_delete.rs b/common/client-libs/validator-client/src/rest_requests/node_unregister_delete.rs new file mode 100644 index 0000000000..a899724b0e --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/node_unregister_delete.rs @@ -0,0 +1,52 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::DefaultRESTResponse; +use reqwest::{Method, Url}; + +pub struct Request { + url: Url, +} + +impl RESTRequest for Request { + const METHOD: Method = Method::DELETE; + const RELATIVE_PATH: &'static str = "/api/mixmining/register"; + type JsonPayload = (); + type ExpectedJsonResponse = DefaultRESTResponse; + + fn new( + base_url: &str, + path_params: Option>, + _: Option>, + _: Option, + ) -> Result { + // node unregister requires single path param - the node id + let path_params = path_params.ok_or_else(|| RESTRequestError::InvalidPathParams)?; + if path_params.len() != 1 { + return Err(RESTRequestError::InvalidPathParams); + } + // /api/mixmining/register/{id} + let base = format!("{}{}/{}", base_url, Self::RELATIVE_PATH, path_params[0]); + + let url = + Url::parse(&base).map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + + Ok(Request { url }) + } + + fn url(&self) -> &Url { + &self.url + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/set_reputation_patch.rs b/common/client-libs/validator-client/src/rest_requests/set_reputation_patch.rs new file mode 100644 index 0000000000..3307fcccba --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/set_reputation_patch.rs @@ -0,0 +1,63 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::DefaultRESTResponse; +use reqwest::{Method, Url}; + +pub struct Request { + url: Url, +} + +impl RESTRequest for Request { + const METHOD: Method = Method::PATCH; + const RELATIVE_PATH: &'static str = "/api/mixmining/reputation"; + type JsonPayload = (); + type ExpectedJsonResponse = DefaultRESTResponse; + + fn new( + base_url: &str, + path_params: Option>, + query_params: Option>, + _: Option, + ) -> Result { + // set reputation requires single path param - the node id + // and single query param - what reputation should it be set to + let path_params = path_params.ok_or_else(|| RESTRequestError::InvalidPathParams)?; + if path_params.len() != 1 { + return Err(RESTRequestError::InvalidPathParams); + } + + let query_params = query_params.ok_or_else(|| RESTRequestError::InvalidQueryParams)?; + if query_params.len() != 1 { + return Err(RESTRequestError::InvalidQueryParams); + } + + // /api/mixmining/reputation/{id} + let base = format!("{}{}/{}", base_url, Self::RELATIVE_PATH, path_params[0]); + + let url = Url::parse_with_params(&base, query_params) + .map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + + Ok(Request { url }) + } + + fn url(&self) -> &Url { + &self.url + } + + fn query_param_keys() -> Vec<&'static str> { + vec!["reputation"] + } +} diff --git a/common/client-libs/validator-client/src/rest_requests/topology_get.rs b/common/client-libs/validator-client/src/rest_requests/topology_get.rs new file mode 100644 index 0000000000..71f5ed8b1b --- /dev/null +++ b/common/client-libs/validator-client/src/rest_requests/topology_get.rs @@ -0,0 +1,54 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::models::topology::Topology; +use crate::rest_requests::{PathParam, QueryParam, RESTRequest, RESTRequestError}; +use crate::ErrorResponse; +use reqwest::{Method, Url}; +use serde::Deserialize; + +pub struct Request { + url: Url, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub(crate) enum Response { + Ok(Topology), + Error(ErrorResponse), +} + +impl RESTRequest for Request { + const METHOD: Method = Method::GET; + const RELATIVE_PATH: &'static str = "/api/mixmining/topology"; + + type JsonPayload = (); + type ExpectedJsonResponse = Response; + + fn new( + base_url: &str, + _: Option>, + _: Option>, + _: Option, + ) -> Result { + let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) + .map_err(|err| RESTRequestError::MalformedUrl(err.to_string()))?; + + Ok(Request { url }) + } + + fn url(&self) -> &Url { + &self.url + } +} diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index cde9302788..7a5f1405b3 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -21,21 +21,23 @@ use std::{fs, io}; pub trait NymConfig: Default + Serialize + DeserializeOwned { fn template() -> &'static str; - fn config_file_name() -> String; + fn config_file_name() -> String { + "config.toml".to_string() + } fn default_root_directory() -> PathBuf; // default, most probable, implementations; can be easily overridden where required - fn default_config_directory(id: Option<&str>) -> PathBuf { - Self::default_root_directory() - .join(id.unwrap_or("")) - .join("config") + fn default_config_directory(id: &str) -> PathBuf { + Self::default_root_directory().join(id).join("config") } - fn default_data_directory(id: Option<&str>) -> PathBuf { - Self::default_root_directory() - .join(id.unwrap_or("")) - .join("data") + fn default_data_directory(id: &str) -> PathBuf { + Self::default_root_directory().join(id).join("data") + } + + fn default_config_file_path(id: &str) -> PathBuf { + Self::default_config_directory(id).join(Self::config_file_name()) } fn root_directory(&self) -> PathBuf; @@ -66,11 +68,8 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { ) } - fn load_from_file(custom_location: Option, id: Option<&str>) -> io::Result { - let config_contents = fs::read_to_string( - custom_location - .unwrap_or_else(|| Self::default_config_directory(id).join("config.toml")), - )?; + fn load_from_file(id: &str) -> io::Result { + let config_contents = fs::read_to_string(Self::default_config_file_path(id))?; toml::from_str(&config_contents) .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index f190c743c7..a6c3980e11 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -15,11 +15,11 @@ generic-array = "0.14" hkdf = "0.9" hmac = "0.8" stream-cipher = "0.4" -x25519-dalek = "0.6" -ed25519-dalek = "1.0.0-pre.4" +x25519-dalek = "1.1" +ed25519-dalek = "1.0" log = "0.4" pretty_env_logger = "0.3" -rand = {version = "0.7.3", features = ["wasm-bindgen"]} +rand = { version = "0.7.3", features = ["wasm-bindgen"] } # internal nymsphinx-types = { path = "../nymsphinx/types" } diff --git a/common/crypto/src/asymmetric/encryption/mod.rs b/common/crypto/src/asymmetric/encryption/mod.rs index b8433efcd5..f080717d9f 100644 --- a/common/crypto/src/asymmetric/encryption/mod.rs +++ b/common/crypto/src/asymmetric/encryption/mod.rs @@ -25,23 +25,31 @@ pub const PUBLIC_KEY_SIZE: usize = 32; /// Size of a X25519 shared secret pub const SHARED_SECRET_SIZE: usize = 32; -#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] -pub enum EncryptionKeyError { - InvalidPublicKey, - InvalidPrivateKey, +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum KeyRecoveryError { + InvalidPublicKeyBytes, + InvalidPrivateKeyBytes, + MalformedString(bs58::decode::Error), +} + +impl From for KeyRecoveryError { + fn from(err: bs58::decode::Error) -> Self { + KeyRecoveryError::MalformedString(err) + } } // required for std::error::Error -impl Display for EncryptionKeyError { +impl Display for KeyRecoveryError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - EncryptionKeyError::InvalidPrivateKey => write!(f, "Invalid private key"), - EncryptionKeyError::InvalidPublicKey => write!(f, "Invalid public key"), + KeyRecoveryError::InvalidPrivateKeyBytes => write!(f, "Invalid private key bytes"), + KeyRecoveryError::InvalidPublicKeyBytes => write!(f, "Invalid public key bytes"), + KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err), } } } -impl std::error::Error for EncryptionKeyError {} +impl std::error::Error for KeyRecoveryError {} pub struct KeyPair { pub(crate) private_key: PrivateKey, @@ -72,7 +80,7 @@ impl KeyPair { &self.public_key } - pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result { + pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result { Ok(KeyPair { private_key: PrivateKey::from_bytes(priv_bytes)?, public_key: PublicKey::from_bytes(pub_bytes)?, @@ -100,7 +108,7 @@ impl PemStorableKeyPair for KeyPair { } } -#[derive(Debug, Copy, Clone)] +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] pub struct PublicKey(x25519_dalek::PublicKey); impl PublicKey { @@ -108,9 +116,9 @@ impl PublicKey { *self.0.as_bytes() } - pub fn from_bytes(b: &[u8]) -> Result { + pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PUBLIC_KEY_SIZE { - return Err(EncryptionKeyError::InvalidPublicKey); + return Err(KeyRecoveryError::InvalidPublicKeyBytes); } let mut bytes = [0; PUBLIC_KEY_SIZE]; bytes.copy_from_slice(&b[..PUBLIC_KEY_SIZE]); @@ -121,16 +129,14 @@ impl PublicKey { bs58::encode(&self.to_bytes()).into_string() } - pub fn from_base58_string>(val: S) -> Result { - let bytes = bs58::decode(val.into()) - .into_vec() - .expect("TODO: deal with this failure case"); + pub fn from_base58_string>(val: S) -> Result { + let bytes = bs58::decode(val.into()).into_vec()?; Self::from_bytes(&bytes) } } impl PemStorableKey for PublicKey { - type Error = EncryptionKeyError; + type Error = KeyRecoveryError; fn pem_type() -> &'static str { "X25519 PUBLIC KEY" @@ -159,9 +165,9 @@ impl PrivateKey { self.0.to_bytes() } - pub fn from_bytes(b: &[u8]) -> Result { + pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PRIVATE_KEY_SIZE { - return Err(EncryptionKeyError::InvalidPrivateKey); + return Err(KeyRecoveryError::InvalidPrivateKeyBytes); } let mut bytes = [0; 32]; bytes.copy_from_slice(&b[..PRIVATE_KEY_SIZE]); @@ -172,10 +178,8 @@ impl PrivateKey { bs58::encode(&self.to_bytes()).into_string() } - pub fn from_base58_string>(val: S) -> Result { - let bytes = bs58::decode(val.into()) - .into_vec() - .expect("TODO: deal with this failure case"); + pub fn from_base58_string>(val: S) -> Result { + let bytes = bs58::decode(val.into()).into_vec()?; Self::from_bytes(&bytes) } @@ -186,7 +190,7 @@ impl PrivateKey { } impl PemStorableKey for PrivateKey { - type Error = EncryptionKeyError; + type Error = KeyRecoveryError; fn pem_type() -> &'static str { "X25519 PRIVATE KEY" diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index 6e00d34ea3..3bec2a786e 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -12,13 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -use bs58; use ed25519_dalek::ed25519::signature::Signature as SignatureTrait; pub use ed25519_dalek::SignatureError; pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH}; use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use rand::{rngs::OsRng, CryptoRng, RngCore}; +use std::fmt::{self, Formatter}; + +#[derive(Debug)] +pub enum KeyRecoveryError { + MalformedBytes(SignatureError), + MalformedString(bs58::decode::Error), +} + +impl From for KeyRecoveryError { + fn from(err: SignatureError) -> Self { + KeyRecoveryError::MalformedBytes(err) + } +} + +impl From for KeyRecoveryError { + fn from(err: bs58::decode::Error) -> Self { + KeyRecoveryError::MalformedString(err) + } +} + +impl fmt::Display for KeyRecoveryError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + KeyRecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err), + KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err), + } + } +} + +impl std::error::Error for KeyRecoveryError {} /// Keypair for usage in ed25519 EdDSA. pub struct KeyPair { @@ -49,7 +78,7 @@ impl KeyPair { &self.public_key } - pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result { + pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result { Ok(KeyPair { private_key: PrivateKey::from_bytes(priv_bytes)?, public_key: PublicKey::from_bytes(pub_bytes)?, @@ -96,7 +125,7 @@ impl PublicKey { self.0.to_bytes() } - pub fn from_bytes(b: &[u8]) -> Result { + pub fn from_bytes(b: &[u8]) -> Result { Ok(PublicKey(ed25519_dalek::PublicKey::from_bytes(b)?)) } @@ -104,10 +133,8 @@ impl PublicKey { bs58::encode(&self.to_bytes()).into_string() } - pub fn from_base58_string>(val: S) -> Result { - let bytes = bs58::decode(val.into()) - .into_vec() - .expect("TODO: deal with this failure case"); + pub fn from_base58_string>(val: S) -> Result { + let bytes = bs58::decode(val.into()).into_vec()?; Self::from_bytes(&bytes) } @@ -117,7 +144,7 @@ impl PublicKey { } impl PemStorableKey for PublicKey { - type Error = SignatureError; + type Error = KeyRecoveryError; fn pem_type() -> &'static str { "ED25519 PUBLIC KEY" @@ -147,7 +174,7 @@ impl PrivateKey { self.0.to_bytes() } - pub fn from_bytes(b: &[u8]) -> Result { + pub fn from_bytes(b: &[u8]) -> Result { Ok(PrivateKey(ed25519_dalek::SecretKey::from_bytes(b)?)) } @@ -155,10 +182,8 @@ impl PrivateKey { bs58::encode(&self.to_bytes()).into_string() } - pub fn from_base58_string>(val: S) -> Result { - let bytes = bs58::decode(val.into()) - .into_vec() - .expect("TODO: deal with this failure case"); + pub fn from_base58_string>(val: S) -> Result { + let bytes = bs58::decode(val.into()).into_vec()?; Self::from_bytes(&bytes) } @@ -171,7 +196,7 @@ impl PrivateKey { } impl PemStorableKey for PrivateKey { - type Error = SignatureError; + type Error = KeyRecoveryError; fn pem_type() -> &'static str { "ED25519 PRIVATE KEY" diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml new file mode 100644 index 0000000000..c1fe96df6e --- /dev/null +++ b/common/mixnode-common/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "mixnode-common" +version = "0.1.0" +authors = ["Jędrzej Stuczyński "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# using 4.0.0 release candidate as it's faster than 3.X and more importantly it resolves edge cases deadlocks +dashmap = "4.0.0-rc6" +futures = "0.3" +log = "0.4" +nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" } +nymsphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } +nymsphinx-addressing = { path = "../nymsphinx/addressing" } +nymsphinx-forwarding = { path = "../nymsphinx/forwarding" } +nymsphinx-framing = { path = "../nymsphinx/framing" } +nymsphinx-params = { path = "../nymsphinx/params" } +nymsphinx-types = { path = "../nymsphinx/types" } +tokio = { version = "0.2", features = ["time", "macros", "rt-core"] } \ No newline at end of file diff --git a/common/mixnode-common/src/cached_packet_processor/cache.rs b/common/mixnode-common/src/cached_packet_processor/cache.rs new file mode 100644 index 0000000000..3fa9f82d81 --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/cache.rs @@ -0,0 +1,176 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use dashmap::{DashMap, ElementGuard}; +use futures::channel::mpsc; +use log::*; +use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue}; +use nymsphinx_types::header::keys::RoutingKeys; +use nymsphinx_types::SharedSecret; +use std::sync::Arc; +use tokio::stream::StreamExt; +use tokio::time::{Duration, Error as TimeError}; + +type CachedKeys = (Option, RoutingKeys); + +pub(super) struct KeyCache { + vpn_key_cache: Arc>, + invalidator_sender: InvalidatorActionSender, + cache_entry_ttl: Duration, +} + +impl Drop for KeyCache { + fn drop(&mut self) { + debug!("dropping key cache"); + if self + .invalidator_sender + .unbounded_send(InvalidatorAction::Stop) + .is_err() + { + debug!("invalidator has already been dropped") + } + } +} + +impl KeyCache { + pub(super) fn new(cache_entry_ttl: Duration) -> Self { + let cache = Arc::new(DashMap::new()); + let (sender, receiver) = mpsc::unbounded(); + + let mut invalidator = CacheInvalidator { + entry_ttl: cache_entry_ttl, + vpn_key_cache: Arc::clone(&cache), + expirations: NonExhaustiveDelayQueue::new(), + action_receiver: receiver, + }; + + // TODO: is it possible to avoid tokio::spawn here and make it semi-runtime agnostic? + tokio::spawn(async move { invalidator.run().await }); + + KeyCache { + vpn_key_cache: cache, + invalidator_sender: sender, + cache_entry_ttl, + } + } + + pub(super) fn insert(&self, key: SharedSecret, cached_keys: CachedKeys) -> bool { + trace!("inserting {:?} into the cache", key); + let insertion_result = self.vpn_key_cache.insert(key, cached_keys); + if !insertion_result { + debug!("{:?} was put into the cache", key); + // this shouldn't really happen, but don't insert entry to invalidator if it was already + // in the cache + self.invalidator_sender + .unbounded_send(InvalidatorAction::Insert(key)) + .expect("Cache invalidator has crashed!"); + } + insertion_result + } + + // ElementGuard has Deref for CachedKeys so that's fine + pub(super) fn get(&self, key: &SharedSecret) -> Option> { + self.vpn_key_cache.get(key) + } + + pub(super) fn cache_entry_ttl(&self) -> Duration { + self.cache_entry_ttl + } + + #[cfg(test)] + pub(super) fn is_empty(&self) -> bool { + self.vpn_key_cache.is_empty() + } + + #[cfg(test)] + pub(super) fn len(&self) -> usize { + self.vpn_key_cache.len() + } +} + +enum InvalidatorAction { + Insert(SharedSecret), + Stop, +} + +type InvalidatorActionSender = mpsc::UnboundedSender; +type InvalidatorActionReceiver = mpsc::UnboundedReceiver; + +struct CacheInvalidator { + entry_ttl: Duration, + vpn_key_cache: Arc>, + expirations: NonExhaustiveDelayQueue, + action_receiver: InvalidatorActionReceiver, +} + +// we do not have a strong requirement of invalidating things EXACTLY after their TTL expires. +// we want them to be eventually gone in a relatively timely manner. +impl CacheInvalidator { + // two obvious ways I've seen of running this were as follows: + // + // 1) every X second, purge all expired entries + // pros: simpler to implement + // cons: will require to obtain write lock multiple times in quick succession + // + // 2) purge entry as soon as it expires + // pros: the lock situation will be spread more in time + // cons: possibly less efficient? + + fn handle_expired(&mut self, expired: Option, TimeError>>) { + let expired = expired.expect("the queue has unexpectedly terminated!"); + let expired_entry = expired.expect("Encountered timer issue within the runtime!"); + + debug!( + "{:?} has expired and will be removed", + expired_entry.get_ref() + ); + + if !self.vpn_key_cache.remove(&expired_entry.into_inner()) { + error!("Tried to remove vpn cache entry for non-existent key!") + } + } + + /// Handles received action. Return `bool` indicates whether the invalidator + /// should terminate. + fn handle_action(&mut self, action: Option) -> bool { + if action.is_none() { + return true; + } + + match action.unwrap() { + InvalidatorAction::Stop => true, + InvalidatorAction::Insert(shared_secret) => { + self.expirations.insert(shared_secret, self.entry_ttl); + false + } + } + } + + async fn run(&mut self) { + loop { + tokio::select! { + expired = self.expirations.next() => { + self.handle_expired(expired); + } + action = self.action_receiver.next() => { + if self.handle_action(action) { + info!("Stopping cache invalidator"); + return + } + } + + } + } + } +} diff --git a/common/mixnode-common/src/cached_packet_processor/error.rs b/common/mixnode-common/src/cached_packet_processor/error.rs new file mode 100644 index 0000000000..ed87dac37e --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/error.rs @@ -0,0 +1,72 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use nymsphinx_acknowledgements::surb_ack::SURBAckRecoveryError; +use nymsphinx_addressing::nodes::NymNodeRoutingAddressError; +use nymsphinx_types::Error as SphinxError; +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug)] +pub enum MixProcessingError { + SphinxProcessingError(SphinxError), + InvalidHopAddress(NymNodeRoutingAddressError), + NoSURBAckInFinalHop, + MalformedSURBAck(SURBAckRecoveryError), +} + +impl From for MixProcessingError { + // for time being just have a single error instance for all possible results of SphinxError + fn from(err: SphinxError) -> Self { + use MixProcessingError::*; + + SphinxProcessingError(err) + } +} + +impl From for MixProcessingError { + fn from(err: NymNodeRoutingAddressError) -> Self { + use MixProcessingError::*; + + InvalidHopAddress(err) + } +} + +impl From for MixProcessingError { + fn from(err: SURBAckRecoveryError) -> Self { + use MixProcessingError::*; + + MalformedSURBAck(err) + } +} + +impl Display for MixProcessingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + MixProcessingError::SphinxProcessingError(sphinx_err) => { + write!(f, "Sphinx Processing Error - {}", sphinx_err) + } + MixProcessingError::InvalidHopAddress(address_err) => { + write!(f, "Invalid Hop Address - {:?}", address_err) + } + MixProcessingError::NoSURBAckInFinalHop => { + write!(f, "No SURBAck present in the final hop data") + } + MixProcessingError::MalformedSURBAck(surb_ack_err) => { + write!(f, "Malformed SURBAck - {:?}", surb_ack_err) + } + } + } +} + +impl std::error::Error for MixProcessingError {} diff --git a/common/mixnode-common/src/cached_packet_processor/mod.rs b/common/mixnode-common/src/cached_packet_processor/mod.rs new file mode 100644 index 0000000000..3bca3d7611 --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/mod.rs @@ -0,0 +1,17 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod cache; +pub mod error; +pub mod processor; diff --git a/common/mixnode-common/src/cached_packet_processor/processor.rs b/common/mixnode-common/src/cached_packet_processor/processor.rs new file mode 100644 index 0000000000..74343241a5 --- /dev/null +++ b/common/mixnode-common/src/cached_packet_processor/processor.rs @@ -0,0 +1,483 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::cached_packet_processor::cache::KeyCache; +use crate::cached_packet_processor::error::MixProcessingError; +use log::*; +use nymsphinx_acknowledgements::surb_ack::SURBAck; +use nymsphinx_addressing::nodes::NymNodeRoutingAddress; +use nymsphinx_forwarding::packet::MixPacket; +use nymsphinx_framing::packet::FramedSphinxPacket; +use nymsphinx_params::{PacketMode, PacketSize}; +use nymsphinx_types::header::keys::RoutingKeys; +use nymsphinx_types::{ + Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, Payload, PrivateKey, + ProcessedPacket, SharedSecret, SphinxHeader, SphinxPacket, +}; +use std::convert::TryFrom; +use std::sync::Arc; +use tokio::time::Duration; + +type ForwardAck = MixPacket; +type CachedKeys = (Option, RoutingKeys); + +pub struct ProcessedFinalHop { + pub destination: DestinationAddressBytes, + pub forward_ack: Option, + pub message: Vec, +} + +pub enum MixProcessingResult { + ForwardHop(MixPacket), + FinalHop(ProcessedFinalHop), +} + +pub struct CachedPacketProcessor { + sphinx_key: Arc, + vpn_key_cache: KeyCache, +} + +impl CachedPacketProcessor { + /// Creates new instance of `CachedPacketProcessor` + pub fn new(sphinx_key: PrivateKey, cache_entry_ttl: Duration) -> Self { + CachedPacketProcessor { + sphinx_key: Arc::new(sphinx_key), + vpn_key_cache: KeyCache::new(cache_entry_ttl), + } + } + + /// Clones `self` without the `vpn_key_cache`. + pub fn clone_without_cache(&self) -> Self { + CachedPacketProcessor { + sphinx_key: self.sphinx_key.clone(), + vpn_key_cache: KeyCache::new(self.vpn_key_cache.cache_entry_ttl()), + } + } + + /// A naive way of delaying packet. + async fn delay_packet(&self, delay: SphinxDelay) { + // TODO: this should perhaps be replaced with a `DelayQueue` + tokio::time::delay_for(delay.to_duration()).await; + } + + /// Recomputes routing keys for the given initial secret. + fn recompute_routing_keys(&self, initial_secret: &SharedSecret) -> RoutingKeys { + SphinxHeader::compute_routing_keys(initial_secret, &self.sphinx_key) + } + + /// Performs a fresh sphinx unwrapping using no cache. + fn perform_initial_sphinx_packet_processing( + &self, + packet: SphinxPacket, + ) -> Result { + packet.process(&self.sphinx_key).map_err(|err| { + warn!("Failed to unwrap Sphinx packet: {:?}", err); + MixProcessingError::SphinxProcessingError(err) + }) + } + + /// Unwraps sphinx packet using already cached keys. + fn perform_initial_sphinx_packet_processing_with_cached_keys( + &self, + packet: SphinxPacket, + keys: &CachedKeys, + ) -> Result { + packet + .process_with_derived_keys(&keys.0, &keys.1) + .map_err(|err| { + warn!("Failed to unwrap Sphinx packet: {:?}", err); + MixProcessingError::SphinxProcessingError(err) + }) + } + + /// Stores the keys corresponding to the packet that was just processed. + fn cache_keys(&self, initial_secret: SharedSecret, processed_packet: &ProcessedPacket) { + let new_shared_secret = processed_packet.shared_secret(); + let routing_keys = self.recompute_routing_keys(&initial_secret); + if self + .vpn_key_cache + .insert(initial_secret, (new_shared_secret, routing_keys)) + { + debug!("Other thread has already cached keys for this secret!") + } + } + + /// Takes the received framed packet and tries to unwrap it from the sphinx encryption. + /// For any vpn packets it will try to re-use cached keys and if none are available, + /// after first processing, the keys are going to get cached. + fn perform_initial_unwrapping( + &self, + received: FramedSphinxPacket, + ) -> Result { + let packet_mode = received.packet_mode(); + let sphinx_packet = received.into_inner(); + let initial_secret = sphinx_packet.shared_secret(); + + // try to use pre-computed keys only for the vpn-packets + if packet_mode.is_vpn() { + if let Some(cached_keys) = self.vpn_key_cache.get(&initial_secret) { + return self.perform_initial_sphinx_packet_processing_with_cached_keys( + sphinx_packet, + cached_keys.value(), + ); + } + } + + let processing_result = self.perform_initial_sphinx_packet_processing(sphinx_packet); + // quicker exit because this will be the most common case + if !packet_mode.is_vpn() { + return processing_result; + } + + if let Ok(processed_packet) = processing_result.as_ref() { + // if we managed to process packet we saw for the first time AND it's a vpn packet + // cache the keys + self.cache_keys(initial_secret, processed_packet); + } + processing_result + } + + /// Processed received forward hop packet - tries to extract next hop address, delays it + /// if it was not a vpn packet and packs all the data in a way that can be easily sent + /// to the next hop. + async fn process_forward_hop( + &self, + packet: SphinxPacket, + forward_address: NodeAddressBytes, + delay: SphinxDelay, + packet_mode: PacketMode, + ) -> Result { + let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?; + + if !packet_mode.is_vpn() { + self.delay_packet(delay).await; + } + + let mix_packet = MixPacket::new(next_hop_address, packet, packet_mode); + Ok(MixProcessingResult::ForwardHop(mix_packet)) + } + + /// Split data extracted from the final hop sphinx packet into a SURBAck and message + /// that should get delivered to a client. + fn split_hop_data_into_ack_and_message( + &self, + mut extracted_data: Vec, + ) -> Result<(Vec, Vec), MixProcessingError> { + // in theory it's impossible for this to fail since it managed to go into correct `match` + // branch at the caller + if extracted_data.len() < SURBAck::len() { + return Err(MixProcessingError::NoSURBAckInFinalHop); + } + + let message = extracted_data.split_off(SURBAck::len()); + let ack_data = extracted_data; + Ok((ack_data, message)) + } + + /// Tries to extract a SURBAck that could be sent back into the mix network and message + /// that should get delivered to a client from received Sphinx packet. + fn split_into_ack_and_message( + &self, + data: Vec, + packet_size: PacketSize, + packet_mode: PacketMode, + ) -> Result<(Option, Vec), MixProcessingError> { + match packet_size { + PacketSize::ACKPacket => { + trace!("received an ack packet!"); + Ok((None, data)) + } + PacketSize::RegularPacket | PacketSize::ExtendedPacket => { + trace!("received a normal packet!"); + let (ack_data, message) = self.split_hop_data_into_ack_and_message(data)?; + let (ack_first_hop, ack_packet) = SURBAck::try_recover_first_hop_packet(&ack_data)?; + let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_mode); + Ok((Some(forward_ack), message)) + } + } + } + + /// Processed received final hop packet - tries to extract SURBAck out of it (assuming the + /// packet itself is not an ACK) and splits it from the message that should get delivered + /// to the destination. + fn process_final_hop( + &self, + destination: DestinationAddressBytes, + payload: Payload, + packet_size: PacketSize, + packet_mode: PacketMode, + ) -> Result { + let packet_message = payload.recover_plaintext()?; + + let (forward_ack, message) = + self.split_into_ack_and_message(packet_message, packet_size, packet_mode)?; + + Ok(MixProcessingResult::FinalHop(ProcessedFinalHop { + destination, + forward_ack, + message, + })) + } + + /// Performs final processing for the unwrapped packet based on whether it was a forward hop + /// or a final hop. + async fn perform_final_processing( + &self, + packet: ProcessedPacket, + packet_size: PacketSize, + packet_mode: PacketMode, + ) -> Result { + match packet { + ProcessedPacket::ForwardHop(packet, address, delay) => { + self.process_forward_hop(packet, address, delay, packet_mode) + .await + } + // right now there's no use for the surb_id included in the header - probably it should get removed from the + // sphinx all together? + ProcessedPacket::FinalHop(destination, _, payload) => { + self.process_final_hop(destination, payload, packet_size, packet_mode) + } + } + } + + pub async fn process_received( + &self, + received: FramedSphinxPacket, + ) -> Result { + // explicit packet size will help to correctly parse final hop + let packet_size = received.packet_size(); + let packet_mode = received.packet_mode(); + + // unwrap the sphinx packet and if possible and appropriate, cache keys + let processed_packet = self.perform_initial_unwrapping(received)?; + + // for forward, non-vpn packets delay for specified amount, + // for final packets, extract SURBAck + self.perform_final_processing(processed_packet, packet_size, packet_mode) + .await + } +} + +// TODO: what more could we realistically test here? +#[cfg(test)] +mod tests { + use super::*; + use nymsphinx_types::builder::SphinxPacketBuilder; + use nymsphinx_types::crypto::keygen; + use nymsphinx_types::{ + Destination, Node, PublicKey, DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, + }; + use std::convert::TryInto; + use std::net::SocketAddr; + + fn fixture() -> CachedPacketProcessor { + let local_keys = keygen(); + CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)) + } + + fn make_valid_final_sphinx_packet(size: PacketSize, public_key: PublicKey) -> SphinxPacket { + let routing_address: NymNodeRoutingAddress = + NymNodeRoutingAddress::from("127.0.0.1:1789".parse::().unwrap()); + + let node = Node::new(routing_address.try_into().unwrap(), public_key); + + let destination = Destination::new( + DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), + [4u8; IDENTIFIER_LENGTH], + ); + + // required until https://github.com/nymtech/sphinx/issues/71 is fixed + let dummy_delay = SphinxDelay::new_from_nanos(42); + + SphinxPacketBuilder::new() + .with_payload_size(size.payload_size()) + .build_packet(b"foomp".to_vec(), &[node], &destination, &[dummy_delay]) + .unwrap() + } + + fn make_valid_forward_sphinx_packet(size: PacketSize, public_key: PublicKey) -> SphinxPacket { + let routing_address: NymNodeRoutingAddress = + NymNodeRoutingAddress::from("127.0.0.1:1789".parse::().unwrap()); + + let some_node_key = keygen(); + let route = [ + Node::new(routing_address.try_into().unwrap(), public_key), + Node::new(routing_address.try_into().unwrap(), some_node_key.1), + ]; + + let destination = Destination::new( + DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), + [4u8; IDENTIFIER_LENGTH], + ); + + let delays = [ + SphinxDelay::new_from_nanos(42), + SphinxDelay::new_from_nanos(42), + ]; + + SphinxPacketBuilder::new() + .with_payload_size(size.payload_size()) + .build_packet(b"foomp".to_vec(), &route, &destination, &delays) + .unwrap() + } + + #[tokio::test] + async fn recomputing_routing_keys_derives_correct_set_of_keys() { + let processor = fixture(); + let (_, initial_secret) = keygen(); + assert_eq!( + processor.recompute_routing_keys(&initial_secret), + SphinxHeader::compute_routing_keys(&initial_secret, &processor.sphinx_key) + ) + } + + #[tokio::test] + async fn caching_keys_updates_local_state_for_final_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let final_hop = make_valid_final_sphinx_packet(Default::default(), local_keys.1); + let initial_secret = final_hop.shared_secret(); + let processed = final_hop.process(&processor.sphinx_key).unwrap(); + + processor.cache_keys(initial_secret, &processed); + let cache_entry = processor.vpn_key_cache.get(&initial_secret).unwrap(); + + let (cached_secret, cached_routing_keys) = cache_entry.value(); + + assert!(cached_secret.is_none()); + let recomputed_keys = processor.recompute_routing_keys(&initial_secret); + // if one key matches then all keys must match (or there is a serious bug inside sphinx) + assert_eq!( + cached_routing_keys.stream_cipher_key, + recomputed_keys.stream_cipher_key + ); + } + + #[tokio::test] + async fn caching_keys_updates_local_state_for_forward_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let forward_hop = make_valid_forward_sphinx_packet(Default::default(), local_keys.1); + let initial_secret = forward_hop.shared_secret(); + let processed = forward_hop.process(&processor.sphinx_key).unwrap(); + + processor.cache_keys(initial_secret, &processed); + let cache_entry = processor.vpn_key_cache.get(&initial_secret).unwrap(); + + let (cached_secret, cached_routing_keys) = cache_entry.value(); + + assert_eq!( + cached_secret.as_ref().unwrap(), + processed.shared_secret().as_ref().unwrap() + ); + let recomputed_keys = processor.recompute_routing_keys(&initial_secret); + // if one key matches then all keys must match (or there is a serious bug inside sphinx) + assert_eq!( + cached_routing_keys.stream_cipher_key, + recomputed_keys.stream_cipher_key + ); + } + + #[tokio::test] + async fn performing_initial_unwrapping_caches_keys_if_vpnmode_used_for_final_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let final_hop = make_valid_final_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(final_hop, PacketMode::VPN); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert_eq!(processor.vpn_key_cache.len(), 1); + } + + #[tokio::test] + async fn performing_initial_unwrapping_caches_keys_if_vpnmode_used_for_forward_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let forward_hop = make_valid_forward_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(forward_hop, PacketMode::VPN); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert_eq!(processor.vpn_key_cache.len(), 1); + } + + #[tokio::test] + async fn performing_initial_unwrapping_does_no_caching_for_mix_mode_for_final_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let final_hop = make_valid_final_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(final_hop, PacketMode::Mix); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert!(processor.vpn_key_cache.is_empty()); + } + + #[tokio::test] + async fn performing_initial_unwrapping_does_no_caching_for_mix_mode_for_forward_hop() { + let local_keys = keygen(); + let processor = CachedPacketProcessor::new(local_keys.0, Duration::from_secs(30)); + assert!(processor.vpn_key_cache.is_empty()); + + let forward_hop = make_valid_forward_sphinx_packet(Default::default(), local_keys.1); + let framed = FramedSphinxPacket::new(forward_hop, PacketMode::Mix); + + processor.perform_initial_unwrapping(framed).unwrap(); + assert!(processor.vpn_key_cache.is_empty()); + } + + #[tokio::test] + async fn splitting_hop_data_works_for_sufficiently_long_payload() { + let processor = fixture(); + + let short_data = vec![42u8]; + assert!(processor + .split_hop_data_into_ack_and_message(short_data) + .is_err()); + + let sufficient_data = vec![42u8; SURBAck::len()]; + let (ack, data) = processor + .split_hop_data_into_ack_and_message(sufficient_data.clone()) + .unwrap(); + assert_eq!(sufficient_data, ack); + assert!(data.is_empty()); + + let long_data = vec![42u8; SURBAck::len() * 5]; + let (ack, data) = processor + .split_hop_data_into_ack_and_message(long_data) + .unwrap(); + assert_eq!(ack.len(), SURBAck::len()); + assert_eq!(data.len(), SURBAck::len() * 4) + } + + #[tokio::test] + async fn splitting_into_ack_and_message_returns_whole_data_for_ack() { + let processor = fixture(); + + let data = vec![42u8; SURBAck::len() + 10]; + let (ack, message) = processor + .split_into_ack_and_message(data.clone(), PacketSize::ACKPacket, Default::default()) + .unwrap(); + assert!(ack.is_none()); + assert_eq!(data, message) + } +} diff --git a/validator/src/services/mod.rs b/common/mixnode-common/src/lib.rs similarity index 94% rename from validator/src/services/mod.rs rename to common/mixnode-common/src/lib.rs index 90af524fc8..2b65a961b7 100644 --- a/validator/src/services/mod.rs +++ b/common/mixnode-common/src/lib.rs @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod mixmining; +pub mod cached_packet_processor; diff --git a/common/client-libs/directory-client/models/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml similarity index 56% rename from common/client-libs/directory-client/models/Cargo.toml rename to common/nonexhaustive-delayqueue/Cargo.toml index 3f2c9c352c..65dea23278 100644 --- a/common/client-libs/directory-client/models/Cargo.toml +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "directory-client-models" +name = "nonexhaustive-delayqueue" version = "0.1.0" authors = ["JÄ™drzej StuczyÅ„ski "] edition = "2018" @@ -7,7 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = { version = "1.0", features = ["derive"] } - -crypto = { path = "../../../crypto" } -topology = { path = "../../../topology" } +tokio = { version = "0.2", features = ["stream", "time"] } \ No newline at end of file diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/ack_delay_queue.rs b/common/nonexhaustive-delayqueue/src/lib.rs similarity index 69% rename from clients/client-core/src/client/real_messages_control/acknowledgement_control/ack_delay_queue.rs rename to common/nonexhaustive-delayqueue/src/lib.rs index a27dc9c6a2..b2903546e9 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/ack_delay_queue.rs +++ b/common/nonexhaustive-delayqueue/src/lib.rs @@ -12,32 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures::Stream; use std::pin::Pin; use std::task::{Context, Poll, Waker}; use std::time::Duration; -use tokio::time::{ - delay_queue::{self, Expired}, - DelayQueue, -}; +use tokio::stream::Stream; +pub use tokio::time::delay_queue::Expired; +use tokio::time::{delay_queue, DelayQueue}; -// works under assumption that it will be used inside a loop, where we never want a `None` -// TODO: perhaps this should/could be renamed and moved to common/utils (and expose all inner methods?) -pub struct AckDelayQueue { +pub type QueueKey = delay_queue::Key; + +/// A variant of tokio's `DelayQueue`, such that its `Stream` implementation will never return a 'None'. +pub struct NonExhaustiveDelayQueue { inner: DelayQueue, waker: Option, } // more methods of underlying DelayQueue will get exposed as we need them -impl AckDelayQueue { +impl NonExhaustiveDelayQueue { pub fn new() -> Self { - AckDelayQueue { + NonExhaustiveDelayQueue { inner: DelayQueue::new(), waker: None, } } - pub fn insert(&mut self, value: T, timeout: Duration) -> delay_queue::Key { + pub fn insert(&mut self, value: T, timeout: Duration) -> QueueKey { let key = self.inner.insert(value, timeout); if let Some(waker) = self.waker.take() { // we were waiting for an item - wake the executor! @@ -46,12 +45,14 @@ impl AckDelayQueue { key } - pub fn remove(&mut self, key: &delay_queue::Key) -> Expired { + // TODO: it seems like this one can cause panic in very rare edge cases, however, + // I can't seem to be able to reproduce it at all. + pub fn remove(&mut self, key: &QueueKey) -> Expired { self.inner.remove(key) } } -impl Stream for AckDelayQueue { +impl Stream for NonExhaustiveDelayQueue { type Item = as Stream>::Item; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -66,3 +67,10 @@ impl Stream for AckDelayQueue { } } } + +// #[cfg(test)] +// mod tests { +// use super::*; +// +// +// } diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index be7b5b5831..083a4d8833 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -15,6 +15,7 @@ nymsphinx-addressing = { path = "addressing" } nymsphinx-anonymous-replies = { path = "anonymous-replies" } nymsphinx-chunking = { path = "chunking" } nymsphinx-cover = { path = "cover" } +nymsphinx-forwarding = { path = "forwarding" } nymsphinx-params = { path = "params" } nymsphinx-types = { path = "types" } @@ -26,4 +27,8 @@ topology = { path = "../topology" } # do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require # net2 via tokio-util -> tokio -> mio -> net2 [target."cfg(not(target_arch = \"wasm32\"))".dependencies.nymsphinx-framing] -path = "framing" \ No newline at end of file +path = "framing" + +[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] +version = "0.2" +features = ["sync"] diff --git a/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index 8722aa0ac6..e1edd16e63 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -21,7 +21,7 @@ use nymsphinx_params::DEFAULT_NUM_MIX_HOPS; use nymsphinx_types::builder::SphinxPacketBuilder; use nymsphinx_types::{ delays::{self, Delay}, - SphinxPacket, + EphemeralSecret, SphinxPacket, }; use rand::{CryptoRng, RngCore}; use std::convert::TryFrom; @@ -50,6 +50,7 @@ impl SURBAck { marshaled_fragment_id: [u8; 5], average_delay: time::Duration, topology: &NymTopology, + initial_sphinx_secret: Option<&EphemeralSecret>, ) -> Result where R: RngCore + CryptoRng, @@ -61,15 +62,19 @@ impl SURBAck { let surb_ack_payload = prepare_identifier(rng, ack_key, marshaled_fragment_id); - // once merged, that's an easy rng injection point for sphinx packets : ) - let surb_ack_packet = SphinxPacketBuilder::new() - .with_payload_size(PacketSize::ACKPacket.payload_size()) + let mut surb_builder = + SphinxPacketBuilder::new().with_payload_size(PacketSize::ACKPacket.payload_size()); + if let Some(initial_secret) = initial_sphinx_secret { + surb_builder = surb_builder.with_initial_secret(initial_secret); + } + + let surb_ack_packet = surb_builder .build_packet(surb_ack_payload, &route, &destination, &delays) .unwrap(); let expected_total_delay = delays.iter().sum(); let first_hop_address = - NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap(); + NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap(); Ok(SURBAck { surb_ack_packet, diff --git a/common/nymsphinx/addressing/src/clients.rs b/common/nymsphinx/addressing/src/clients.rs index 8295df64ef..1376df59d8 100644 --- a/common/nymsphinx/addressing/src/clients.rs +++ b/common/nymsphinx/addressing/src/clients.rs @@ -22,9 +22,9 @@ const CLIENT_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH; #[derive(Debug)] pub enum RecipientFormattingError { MalformedRecipientError, - MalformedIdentityError(identity::SignatureError), - MalformedEncryptionKeyError(encryption::EncryptionKeyError), - MalformedGatewayError(identity::SignatureError), + MalformedIdentityError(identity::KeyRecoveryError), + MalformedEncryptionKeyError(encryption::KeyRecoveryError), + MalformedGatewayError(identity::KeyRecoveryError), } impl fmt::Display for RecipientFormattingError { @@ -51,8 +51,8 @@ impl fmt::Display for RecipientFormattingError { // since we have Debug and Display might as well slap Error on top of it too impl std::error::Error for RecipientFormattingError {} -impl From for RecipientFormattingError { - fn from(err: encryption::EncryptionKeyError) -> Self { +impl From for RecipientFormattingError { + fn from(err: encryption::KeyRecoveryError) -> Self { RecipientFormattingError::MalformedEncryptionKeyError(err) } } @@ -102,11 +102,11 @@ impl<'de> Deserialize<'de> for Recipient { // this shouldn't panic as we just checked for length recipient_bytes.copy_from_slice(&bytes); - Recipient::try_from_bytes(recipient_bytes).or_else(|_| { - Err(SerdeError::invalid_value( + Recipient::try_from_bytes(recipient_bytes).map_err(|_| { + SerdeError::invalid_value( Unexpected::Other("At least one of the curve points was malformed"), &self, - )) + ) }) } } @@ -251,7 +251,7 @@ mod tests { let recipient = Recipient::new( *client_id_pair.public_key(), - client_enc_pair.public_key().clone(), + *client_enc_pair.public_key(), *gateway_id_pair.public_key(), ); @@ -281,7 +281,7 @@ mod tests { let recipient = Recipient::new( *client_id_pair.public_key(), - client_enc_pair.public_key().clone(), + *client_enc_pair.public_key(), *gateway_id_pair.public_key(), ); diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index 1663e213ec..7c0e15541c 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -103,7 +103,7 @@ impl<'de> Deserialize<'de> for ReplySURB { E: SerdeError, { ReplySURB::from_bytes(bytes) - .or_else(|_| Err(SerdeError::invalid_length(bytes.len(), &self))) + .map_err(|_| SerdeError::invalid_length(bytes.len(), &self)) } } diff --git a/common/nymsphinx/cover/Cargo.toml b/common/nymsphinx/cover/Cargo.toml index d9f5846dfd..49d58431c6 100644 --- a/common/nymsphinx/cover/Cargo.toml +++ b/common/nymsphinx/cover/Cargo.toml @@ -14,5 +14,6 @@ nymsphinx-acknowledgements = { path = "../acknowledgements" } nymsphinx-addressing = { path = "../addressing" } nymsphinx-chunking = { path = "../chunking" } nymsphinx-params = { path = "../params" } +nymsphinx-forwarding = { path = "../forwarding" } nymsphinx-types = { path = "../types" } topology = { path = "../../topology" } \ No newline at end of file diff --git a/common/nymsphinx/cover/src/lib.rs b/common/nymsphinx/cover/src/lib.rs index 1996f44475..18b13b4aad 100644 --- a/common/nymsphinx/cover/src/lib.rs +++ b/common/nymsphinx/cover/src/lib.rs @@ -19,10 +19,13 @@ use nymsphinx_acknowledgements::AckKey; use nymsphinx_addressing::clients::Recipient; use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; use nymsphinx_chunking::fragment::COVER_FRAG_ID; +use nymsphinx_forwarding::packet::MixPacket; use nymsphinx_params::packet_sizes::PacketSize; -use nymsphinx_params::{PacketEncryptionAlgorithm, PacketHkdfAlgorithm, DEFAULT_NUM_MIX_HOPS}; +use nymsphinx_params::{ + PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketMode, DEFAULT_NUM_MIX_HOPS, +}; use nymsphinx_types::builder::SphinxPacketBuilder; -use nymsphinx_types::{delays, Error as SphinxError, SphinxPacket}; +use nymsphinx_types::{delays, Error as SphinxError}; use rand::{CryptoRng, RngCore}; use std::convert::TryFrom; use std::time; @@ -74,6 +77,7 @@ where COVER_FRAG_ID.to_bytes(), average_ack_delay, topology, + None, )?) } @@ -84,7 +88,7 @@ pub fn generate_loop_cover_packet( full_address: &Recipient, average_ack_delay: time::Duration, average_packet_delay: time::Duration, -) -> Result<(NymNodeRoutingAddress, SphinxPacket), CoverMessageError> +) -> Result where R: RngCore + CryptoRng, { @@ -142,9 +146,10 @@ where .unwrap(); let first_hop_address = - NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap(); + NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap(); - Ok((first_hop_address, packet)) + // if client is running in vpn mode, he won't even be sending cover traffic + Ok(MixPacket::new(first_hop_address, packet, PacketMode::Mix)) } /// Helper function used to determine if given message represents a loop cover message. diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml new file mode 100644 index 0000000000..80ab053665 --- /dev/null +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nymsphinx-forwarding" +version = "0.1.0" +authors = ["Jedrzej Stuczynski "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nymsphinx-addressing = { path = "../addressing" } +nymsphinx-params = { path = "../params" } +nymsphinx-types = { path = "../types" } diff --git a/validator/src/network/rest/models/mod.rs b/common/nymsphinx/forwarding/src/lib.rs similarity index 97% rename from validator/src/network/rest/models/mod.rs rename to common/nymsphinx/forwarding/src/lib.rs index 82b6b328e2..f85c8e9362 100644 --- a/validator/src/network/rest/models/mod.rs +++ b/common/nymsphinx/forwarding/src/lib.rs @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod presence; +pub mod packet; diff --git a/common/nymsphinx/forwarding/src/packet.rs b/common/nymsphinx/forwarding/src/packet.rs new file mode 100644 index 0000000000..fbfec653ad --- /dev/null +++ b/common/nymsphinx/forwarding/src/packet.rs @@ -0,0 +1,128 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; +use nymsphinx_params::{PacketMode, PacketSize}; +use nymsphinx_types::SphinxPacket; +use std::convert::TryFrom; +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug)] +pub enum MixPacketFormattingError { + TooFewBytesProvided, + InvalidPacketMode, + InvalidPacketSize(usize), + InvalidAddress, + MalformedSphinxPacket, +} + +impl Display for MixPacketFormattingError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use MixPacketFormattingError::*; + match self { + TooFewBytesProvided => write!(f, "Too few bytes provided to recover from bytes"), + InvalidAddress => write!(f, "address field was incorrectly encoded"), + InvalidPacketSize(actual) => + write!( + f, + "received request had invalid size. (actual: {}, but expected one of: {} (ACK), {} (REGULAR), {} (EXTENDED))", + actual, PacketSize::ACKPacket.size(), PacketSize::RegularPacket.size(), PacketSize::ExtendedPacket.size() + ), + MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"), + InvalidPacketMode => write!(f, "provided packet mode is invalid") + } + } +} + +impl std::error::Error for MixPacketFormattingError {} + +impl From for MixPacketFormattingError { + fn from(_: NymNodeRoutingAddressError) -> Self { + MixPacketFormattingError::InvalidAddress + } +} + +pub struct MixPacket { + next_hop: NymNodeRoutingAddress, + sphinx_packet: SphinxPacket, + packet_mode: PacketMode, +} + +impl MixPacket { + pub fn new( + next_hop: NymNodeRoutingAddress, + sphinx_packet: SphinxPacket, + packet_mode: PacketMode, + ) -> Self { + MixPacket { + next_hop, + sphinx_packet, + packet_mode, + } + } + + pub fn next_hop(&self) -> NymNodeRoutingAddress { + self.next_hop + } + + pub fn sphinx_packet(&self) -> &SphinxPacket { + &self.sphinx_packet + } + + pub fn into_sphinx_packet(self) -> SphinxPacket { + self.sphinx_packet + } + + pub fn packet_mode(&self) -> PacketMode { + self.packet_mode + } + + // the message is formatted as follows: + // PACKET_MODE || FIRST_HOP || SPHINX_PACKET + pub fn try_from_bytes(b: &[u8]) -> Result { + let packet_mode = match PacketMode::try_from(b[0]) { + Ok(mode) => mode, + Err(_) => return Err(MixPacketFormattingError::InvalidPacketMode), + }; + + let next_hop = NymNodeRoutingAddress::try_from_bytes(&b[1..])?; + let addr_offset = next_hop.bytes_min_len(); + + let sphinx_packet_data = &b[addr_offset + 1..]; + let packet_size = sphinx_packet_data.len(); + if PacketSize::get_type(packet_size).is_err() { + Err(MixPacketFormattingError::InvalidPacketSize(packet_size)) + } else { + let sphinx_packet = match SphinxPacket::from_bytes(sphinx_packet_data) { + Ok(packet) => packet, + Err(_) => return Err(MixPacketFormattingError::MalformedSphinxPacket), + }; + + Ok(MixPacket { + next_hop, + sphinx_packet, + packet_mode, + }) + } + } + + pub fn into_bytes(self) -> Vec { + std::iter::once(self.packet_mode as u8) + .chain(self.next_hop.as_bytes().into_iter()) + .chain(self.sphinx_packet.to_bytes().into_iter()) + .collect() + } +} + +// TODO: test for serialization and errors! diff --git a/common/nymsphinx/framing/src/codec.rs b/common/nymsphinx/framing/src/codec.rs new file mode 100644 index 0000000000..a9f21a9f80 --- /dev/null +++ b/common/nymsphinx/framing/src/codec.rs @@ -0,0 +1,339 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::packet::{FramedSphinxPacket, Header}; +use bytes::{Buf, BufMut, BytesMut}; +use nymsphinx_params::packet_modes::InvalidPacketMode; +use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; +use nymsphinx_types::SphinxPacket; +use std::convert::TryFrom; +use std::io; +use tokio_util::codec::{Decoder, Encoder}; + +#[derive(Debug)] +pub enum SphinxCodecError { + InvalidPacketSize, + InvalidPacketMode, + MalformedSphinxPacket, + IoError(io::Error), +} + +impl From for SphinxCodecError { + fn from(err: io::Error) -> Self { + SphinxCodecError::IoError(err) + } +} + +impl Into for SphinxCodecError { + fn into(self) -> io::Error { + match self { + SphinxCodecError::InvalidPacketSize => { + io::Error::new(io::ErrorKind::InvalidInput, "invalid packet size") + } + SphinxCodecError::InvalidPacketMode => { + io::Error::new(io::ErrorKind::InvalidInput, "invalid packet mode") + } + SphinxCodecError::MalformedSphinxPacket => { + io::Error::new(io::ErrorKind::InvalidData, "malformed packet") + } + SphinxCodecError::IoError(err) => err, + } + } +} + +impl From for SphinxCodecError { + fn from(_: InvalidPacketSize) -> Self { + SphinxCodecError::InvalidPacketSize + } +} + +impl From for SphinxCodecError { + fn from(_: InvalidPacketMode) -> Self { + SphinxCodecError::InvalidPacketMode + } +} + +// TODO: in the future it could be extended to have state containing symmetric encryption key +// so that all data could be encrypted easily (alternatively we could just slap TLS) +pub struct SphinxCodec; + +impl Encoder for SphinxCodec { + type Error = SphinxCodecError; + + fn encode(&mut self, item: FramedSphinxPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { + item.header.encode(dst)?; + dst.put(item.packet.to_bytes().as_ref()); + Ok(()) + } +} + +impl Decoder for SphinxCodec { + type Item = FramedSphinxPacket; + type Error = SphinxCodecError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if src.is_empty() { + // can't do anything if we have no bytes, but let's reserve enough for the most + // conservative case, i.e. receiving an ack packet + src.reserve(Header::SIZE + PacketSize::ACKPacket.size()); + return Ok(None); + } + + // because header is so small and simple it makes no point in trying to cache + // this result. It will be just simpler to re-decode it + let header = match Header::decode(src)? { + Some(header) => header, + None => return Ok(None), // we have some data but not enough to get header back + }; + + let sphinx_packet_size = header.packet_size.size(); + let frame_len = Header::SIZE + sphinx_packet_size; + + if src.len() < frame_len { + // we don't have enough bytes to read the rest of frame + src.reserve(sphinx_packet_size); + return Ok(None); + } + + // advance buffer past the header - at this point we have enough bytes + src.advance(Header::SIZE); + let sphinx_packet_bytes = src.split_to(sphinx_packet_size); + let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) { + Ok(sphinx_packet) => sphinx_packet, + // here it could be debatable whether stream is corrupt or not, + // but let's go with the safer approach and assume it is. + Err(_) => return Err(SphinxCodecError::MalformedSphinxPacket), + }; + + let nymsphinx_packet = FramedSphinxPacket { + header, + packet: sphinx_packet, + }; + + // As per docs: + // Before returning from the function, implementations should ensure that the buffer + // has appropriate capacity in anticipation of future calls to decode. + // Failing to do so leads to inefficiency. + + // if we have at least one more byte available, we can reserve enough bytes for + // the entire next frame, if not, we assume the next frame is an ack packet and + // reserve for that. + if !src.is_empty() { + let next_packet_len = match PacketSize::try_from(src[0]) { + Ok(next_packet_len) => next_packet_len, + // the next frame will be malformed but let's leave handling the error to the next + // call to 'decode', as presumably, the current sphinx packet is still valid + Err(_) => return Ok(Some(nymsphinx_packet)), + }; + let next_frame_len = next_packet_len.size() + Header::SIZE; + src.reserve(next_frame_len - 1); + } else { + src.reserve(Header::SIZE + PacketSize::ACKPacket.size()); + } + + Ok(Some(nymsphinx_packet)) + } +} + +#[cfg(test)] +mod packet_encoding { + use super::*; + use nymsphinx_types::builder::SphinxPacketBuilder; + use nymsphinx_types::{ + crypto, Delay as SphinxDelay, Destination, DestinationAddressBytes, Node, NodeAddressBytes, + DESTINATION_ADDRESS_LENGTH, IDENTIFIER_LENGTH, NODE_ADDRESS_LENGTH, + }; + + fn make_valid_sphinx_packet(size: PacketSize) -> SphinxPacket { + let (_, node1_pk) = crypto::keygen(); + let node1 = Node::new( + NodeAddressBytes::from_bytes([5u8; NODE_ADDRESS_LENGTH]), + node1_pk, + ); + let (_, node2_pk) = crypto::keygen(); + let node2 = Node::new( + NodeAddressBytes::from_bytes([4u8; NODE_ADDRESS_LENGTH]), + node2_pk, + ); + let (_, node3_pk) = crypto::keygen(); + let node3 = Node::new( + NodeAddressBytes::from_bytes([2u8; NODE_ADDRESS_LENGTH]), + node3_pk, + ); + + let route = [node1, node2, node3]; + let destination = Destination::new( + DestinationAddressBytes::from_bytes([3u8; DESTINATION_ADDRESS_LENGTH]), + [4u8; IDENTIFIER_LENGTH], + ); + let delays = vec![ + SphinxDelay::new_from_nanos(42), + SphinxDelay::new_from_nanos(42), + SphinxDelay::new_from_nanos(42), + ]; + SphinxPacketBuilder::new() + .with_payload_size(size.payload_size()) + .build_packet(b"foomp".to_vec(), &route, &destination, &delays) + .unwrap() + } + + #[test] + fn whole_packet_can_be_decoded_from_a_valid_encoded_instance() { + let header = Default::default(); + let sphinx_packet = make_valid_sphinx_packet(Default::default()); + let sphinx_bytes = sphinx_packet.to_bytes(); + + let packet = FramedSphinxPacket { + header, + packet: sphinx_packet, + }; + + let mut bytes = BytesMut::new(); + SphinxCodec.encode(packet, &mut bytes).unwrap(); + let decoded = SphinxCodec.decode(&mut bytes).unwrap().unwrap(); + + assert_eq!(decoded.header, header); + assert_eq!(decoded.packet.to_bytes(), sphinx_bytes) + } + + #[cfg(test)] + mod decode_will_allocate_enough_bytes_for_next_call { + use super::*; + + #[test] + fn for_empty_bytes() { + // empty bytes should allocate for header + ack packet + let mut empty_bytes = BytesMut::new(); + assert!(SphinxCodec.decode(&mut empty_bytes).unwrap().is_none()); + assert_eq!( + empty_bytes.capacity(), + Header::SIZE + PacketSize::ACKPacket.size() + ); + } + + #[test] + fn for_bytes_with_header() { + // if header gets decoded there should be enough bytes for the entire frame + let packet_sizes = vec![ + PacketSize::ACKPacket, + PacketSize::RegularPacket, + PacketSize::ExtendedPacket, + ]; + for packet_size in packet_sizes { + let header = Header { + packet_size, + packet_mode: Default::default(), + }; + let mut bytes = BytesMut::new(); + header.encode(&mut bytes).unwrap(); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + + assert_eq!(bytes.capacity(), Header::SIZE + packet_size.size()) + } + } + + #[test] + fn for_full_frame() { + // if full frame is used exactly, there should be enough space for header + ack packet + let packet = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + SphinxCodec.encode(packet, &mut bytes).unwrap(); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert_eq!( + bytes.capacity(), + Header::SIZE + PacketSize::ACKPacket.size() + ); + } + + #[test] + fn for_full_frame_with_extra_byte() { + // if there was at least 1 byte left, there should be enough space for entire next frame + let packet_sizes = vec![ + PacketSize::ACKPacket, + PacketSize::RegularPacket, + PacketSize::ExtendedPacket, + ]; + + for packet_size in packet_sizes { + let first_packet = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + SphinxCodec.encode(first_packet, &mut bytes).unwrap(); + bytes.put_u8(packet_size as u8); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + + assert!(bytes.capacity() >= Header::SIZE + packet_size.size()) + } + } + } + + #[test] + fn can_decode_two_packets_immediately() { + let packet1 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let packet2 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + + SphinxCodec.encode(packet1, &mut bytes).unwrap(); + SphinxCodec.encode(packet2, &mut bytes).unwrap(); + + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + } + + #[test] + fn can_decode_two_packets_in_separate_calls() { + let packet1 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let packet2 = FramedSphinxPacket { + header: Header::default(), + packet: make_valid_sphinx_packet(Default::default()), + }; + + let mut bytes = BytesMut::new(); + let mut bytes_tmp = BytesMut::new(); + + SphinxCodec.encode(packet1, &mut bytes).unwrap(); + SphinxCodec.encode(packet2, &mut bytes_tmp).unwrap(); + + let tmp = bytes_tmp.split_off(100); + bytes.put(bytes_tmp); + + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + + bytes.put(tmp); + + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_some()); + assert!(SphinxCodec.decode(&mut bytes).unwrap().is_none()); + } +} diff --git a/common/nymsphinx/framing/src/lib.rs b/common/nymsphinx/framing/src/lib.rs index 065c087f11..4215f88801 100644 --- a/common/nymsphinx/framing/src/lib.rs +++ b/common/nymsphinx/framing/src/lib.rs @@ -12,136 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -use bytes::{Buf, BufMut, BytesMut}; -use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; -use nymsphinx_types::SphinxPacket; -use std::convert::TryFrom; -use std::io; -use tokio_util::codec::{Decoder, Encoder}; - -#[derive(Debug)] -pub enum SphinxCodecError { - InvalidPacketSize, - MalformedSphinxPacket, - IoError(io::Error), -} - -impl From for SphinxCodecError { - fn from(err: io::Error) -> Self { - SphinxCodecError::IoError(err) - } -} - -impl Into for SphinxCodecError { - fn into(self) -> io::Error { - match self { - SphinxCodecError::InvalidPacketSize => { - io::Error::new(io::ErrorKind::InvalidInput, "invalid packet size") - } - SphinxCodecError::MalformedSphinxPacket => { - io::Error::new(io::ErrorKind::InvalidData, "malformed packet") - } - SphinxCodecError::IoError(err) => err, - } - } -} - -impl From for SphinxCodecError { - fn from(_: InvalidPacketSize) -> Self { - SphinxCodecError::InvalidPacketSize - } -} - -// The SphinxCodec is an extremely simple one, u8 representing one of valid packet -// lengths followed by the actual framed packet -pub struct SphinxCodec; - -impl Encoder for SphinxCodec { - type Error = SphinxCodecError; - - fn encode(&mut self, item: SphinxPacket, dst: &mut BytesMut) -> Result<(), Self::Error> { - let packet_bytes = item.to_bytes(); - let packet_length = packet_bytes.len(); - let packet_size = PacketSize::get_type(packet_length)?; - dst.reserve(1 + packet_size.size()); - dst.put_u8(packet_size as u8); - dst.put(packet_bytes.as_ref()); - Ok(()) - } -} - -impl Decoder for SphinxCodec { - type Item = SphinxPacket; - type Error = SphinxCodecError; - - //https://docs.rs/tokio-util/0.3.1/tokio_util/codec/trait.Decoder.html - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if src.is_empty() { - // can't do anything if we have no bytes - return Ok(None); - } - // we at least have a single byte in the buffer, so we can read the expected - // length of the sphinx packet - let packet_len_flag = src[0]; - let packet_len = PacketSize::try_from(packet_len_flag)?; - - let frame_len = packet_len.size() + 1; // one is due to the flag taking the space - if src.len() < frame_len { - // we don't have enough bytes to read the entire frame - src.reserve(frame_len); - return Ok(None); - } - // we advance the buffer beyond the flag - src.advance(1); - let sphinx_packet_bytes = src.split_to(packet_len.size()); - let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) { - Ok(sphinx_packet) => sphinx_packet, - // here it could be debatable whether stream is corrupt or not, - // but let's go with the safer approach and assume it is. - Err(_) => return Err(SphinxCodecError::MalformedSphinxPacket), - }; - - // As per docs: - // Before returning from the function, implementations should ensure that the buffer - // has appropriate capacity in anticipation of future calls to decode. - // Failing to do so leads to inefficiency. - - // if we have at least one more byte available, we can reserve enough bytes for - // the entire next frame - if !src.is_empty() { - let next_packet_len = match PacketSize::try_from(src[0]) { - Ok(next_packet_len) => next_packet_len, - // the next frame will be malformed but let's leave handling the error to the next - // call to 'decode', as presumably, the current sphinx packet is still valid - Err(_) => return Ok(Some(sphinx_packet)), - }; - let next_frame_len = next_packet_len.size() + 1; - src.reserve(next_frame_len); - } - - Ok(Some(sphinx_packet)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(test)] - #[allow(dead_code)] - fn consume( - codec: &mut SphinxCodec, - bytes: &mut BytesMut, - ) -> Vec, SphinxCodecError>> { - let mut result = Vec::new(); - loop { - match codec.decode(bytes) { - Ok(None) => { - break; - } - output => result.push(output), - } - } - result - } -} +pub mod codec; +pub mod packet; diff --git a/common/nymsphinx/framing/src/packet.rs b/common/nymsphinx/framing/src/packet.rs new file mode 100644 index 0000000000..1296f5e75b --- /dev/null +++ b/common/nymsphinx/framing/src/packet.rs @@ -0,0 +1,165 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::codec::SphinxCodecError; +use bytes::{BufMut, BytesMut}; +use nymsphinx_params::packet_sizes::PacketSize; +use nymsphinx_params::PacketMode; +use nymsphinx_types::SphinxPacket; +use std::convert::TryFrom; + +pub struct FramedSphinxPacket { + /// Contains any metadata helping receiver to handle the underlying packet. + pub(crate) header: Header, + + /// The actual SphinxPacket being sent. + pub(crate) packet: SphinxPacket, +} + +impl FramedSphinxPacket { + pub fn new(packet: SphinxPacket, packet_mode: PacketMode) -> Self { + // If this fails somebody is using the library in a super incorrect way, because they + // already managed to somehow create a sphinx packet + let packet_size = PacketSize::get_type(packet.len()).unwrap(); + FramedSphinxPacket { + header: Header { + packet_size, + packet_mode, + }, + packet, + } + } + + pub fn packet_size(&self) -> PacketSize { + self.header.packet_size + } + + pub fn packet_mode(&self) -> PacketMode { + self.header.packet_mode + } + + pub fn into_inner(self) -> SphinxPacket { + self.packet + } +} + +// Contains any metadata that might be useful for sending between mix nodes. +// TODO: in theory all those data could be put in a single `u8` by setting appropriate bits, +// but would that really be worth it? +#[derive(Debug, Default, PartialEq, Copy, Clone)] +pub struct Header { + /// Represents type and consequently size of the included SphinxPacket. + pub(crate) packet_size: PacketSize, + + /// Represents whether this packet is sent in a `vpn_mode` meaning it should not get delayed + /// and shared keys might get reused. Mixnodes are capable of inferring this mode from the + /// delay values inside the packet header (i.e. being set to 0), however, gateway, being final + /// hop, would be unable to do so. + /// + /// TODO: ask @AP whether this can be sent like this - could it introduce some anonymity issues? + /// (note: this will be behind some encryption, either something implemented by us or some SSL action) + pub(crate) packet_mode: PacketMode, +} + +impl Header { + pub(crate) const SIZE: usize = 2; + + pub(crate) fn encode(&self, dst: &mut BytesMut) -> Result<(), SphinxCodecError> { + // we reserve one byte for `packet_size` and the other for `mode` + dst.reserve(Self::SIZE); + dst.put_u8(self.packet_size as u8); + dst.put_u8(self.packet_mode as u8); + // reserve bytes for the actual packet + dst.reserve(self.packet_size.size()); + Ok(()) + } + + pub(crate) fn decode(src: &mut BytesMut) -> Result, SphinxCodecError> { + if src.len() < Self::SIZE { + // can't do anything if we don't have enough bytes - but reserve enough for the next call + src.reserve(Self::SIZE); + return Ok(None); + } + + Ok(Some(Header { + packet_size: PacketSize::try_from(src[0])?, + packet_mode: PacketMode::try_from(src[1])?, + })) + } +} + +#[cfg(test)] +mod header_encoding { + use super::*; + + #[test] + fn header_can_be_decoded_from_a_valid_encoded_instance() { + let header = Header::default(); + let mut bytes = BytesMut::new(); + header.encode(&mut bytes).unwrap(); + let decoded = Header::decode(&mut bytes).unwrap().unwrap(); + assert_eq!(decoded, header); + } + + #[test] + fn decoding_will_fail_for_unknown_packet_size() { + let unknown_packet_size: u8 = 255; + // make sure this is still 'unknown' for if we make changes in the future + assert!(PacketSize::try_from(unknown_packet_size).is_err()); + + let mut bytes = BytesMut::from([unknown_packet_size, PacketMode::default() as u8].as_ref()); + assert!(Header::decode(&mut bytes).is_err()) + } + + #[test] + fn decoding_will_fail_for_unknown_packet_mode() { + let unknown_packet_mode: u8 = 255; + // make sure this is still 'unknown' for if we make changes in the future + assert!(PacketMode::try_from(unknown_packet_mode).is_err()); + + let mut bytes = BytesMut::from([PacketSize::default() as u8, unknown_packet_mode].as_ref()); + assert!(Header::decode(&mut bytes).is_err()) + } + + #[test] + fn decode_will_allocate_enough_bytes_for_next_call() { + let mut empty_bytes = BytesMut::new(); + let decode_attempt_1 = Header::decode(&mut empty_bytes).unwrap(); + assert!(decode_attempt_1.is_none()); + assert!(empty_bytes.capacity() > Header::SIZE); + + let mut empty_bytes = BytesMut::with_capacity(1); + let decode_attempt_2 = Header::decode(&mut empty_bytes).unwrap(); + assert!(decode_attempt_2.is_none()); + assert!(empty_bytes.capacity() > Header::SIZE); + } + + #[test] + fn header_encoding_reserves_enough_bytes_for_full_sphinx_packet() { + let packet_sizes = vec![ + PacketSize::ACKPacket, + PacketSize::RegularPacket, + PacketSize::ExtendedPacket, + ]; + for packet_size in packet_sizes { + let header = Header { + packet_size, + packet_mode: Default::default(), + }; + let mut bytes = BytesMut::new(); + header.encode(&mut bytes).unwrap(); + assert_eq!(bytes.capacity(), bytes.len() + packet_size.size()) + } + } +} diff --git a/common/nymsphinx/params/src/lib.rs b/common/nymsphinx/params/src/lib.rs index afcdcda36d..9c01458110 100644 --- a/common/nymsphinx/params/src/lib.rs +++ b/common/nymsphinx/params/src/lib.rs @@ -11,12 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crypto::blake3; -pub mod packet_sizes; - use crypto::aes_ctr::Aes128Ctr; +use crypto::blake3; + +// Re-export for ease of use +pub use packet_modes::PacketMode; pub use packet_sizes::PacketSize; +pub mod packet_modes; +pub mod packet_sizes; + // If somebody can provide an argument why it might be reasonable to have more than 255 mix hops, // I will change this to [`usize`] pub const DEFAULT_NUM_MIX_HOPS: u8 = 3; diff --git a/common/nymsphinx/params/src/packet_modes.rs b/common/nymsphinx/params/src/packet_modes.rs new file mode 100644 index 0000000000..1f54d9e851 --- /dev/null +++ b/common/nymsphinx/params/src/packet_modes.rs @@ -0,0 +1,58 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::convert::TryFrom; + +#[derive(Debug)] +pub struct InvalidPacketMode; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PacketMode { + /// Represents 'normal' packet sent through the network that should be delayed by an appropriate + /// value at each hop. + Mix = 0, + + /// Represents a VPN packet that should not be delayed and ideally cached pre-computed keys + /// should be used for unwrapping data. Note that it does not offer the same level of anonymity. + VPN = 1, +} + +impl PacketMode { + pub fn is_mix(self) -> bool { + self == PacketMode::Mix + } + + pub fn is_vpn(self) -> bool { + self == PacketMode::VPN + } +} + +impl TryFrom for PacketMode { + type Error = InvalidPacketMode; + + fn try_from(value: u8) -> std::result::Result { + match value { + _ if value == (PacketMode::Mix as u8) => Ok(Self::Mix), + _ if value == (PacketMode::VPN as u8) => Ok(Self::VPN), + _ => Err(InvalidPacketMode), + } + } +} + +impl Default for PacketMode { + fn default() -> Self { + PacketMode::Mix + } +} diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index 89ecb46e63..9302662f4e 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -28,16 +28,20 @@ const ACK_IV_SIZE: usize = 16; const ACK_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + ACK_IV_SIZE + FRAG_ID_LEN; const EXTENDED_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 32 * 1024; +#[derive(Debug)] pub struct InvalidPacketSize; #[repr(u8)] -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum PacketSize { - RegularPacket = 1, // for example instant messaging use case - ACKPacket = 2, + RegularPacket = 1, + // for sending SURB-ACKs - ExtendedPacket = 3, // for example for streaming fast and furious in uncompressed 10bit 4K HDR quality + ACKPacket = 2, + + // for example for streaming fast and furious in uncompressed 10bit 4K HDR quality + ExtendedPacket = 3, } impl TryFrom for PacketSize { diff --git a/common/nymsphinx/src/lib.rs b/common/nymsphinx/src/lib.rs index 3d13558103..89300a7ca1 100644 --- a/common/nymsphinx/src/lib.rs +++ b/common/nymsphinx/src/lib.rs @@ -22,6 +22,7 @@ pub use nymsphinx_addressing as addressing; pub use nymsphinx_anonymous_replies as anonymous_replies; pub use nymsphinx_chunking as chunking; pub use nymsphinx_cover as cover; +pub use nymsphinx_forwarding as forwarding; #[cfg(not(target_arch = "wasm32"))] pub use nymsphinx_framing as framing; pub use nymsphinx_params as params; diff --git a/common/nymsphinx/src/preparer.rs b/common/nymsphinx/src/preparer/mod.rs similarity index 82% rename from common/nymsphinx/src/preparer.rs rename to common/nymsphinx/src/preparer/mod.rs index 7851a7e0b4..257e2c37b6 100644 --- a/common/nymsphinx/src/preparer.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use self::vpn_manager::VPNManager; use crate::chunking; use crypto::asymmetric::encryption; use crypto::shared_key::new_ephemeral_shared_key; @@ -24,29 +25,31 @@ use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADD use nymsphinx_anonymous_replies::encryption_key::SURBEncryptionKey; use nymsphinx_anonymous_replies::reply_surb::ReplySURB; use nymsphinx_chunking::fragment::{Fragment, FragmentIdentifier}; +use nymsphinx_forwarding::packet::MixPacket; use nymsphinx_params::packet_sizes::PacketSize; use nymsphinx_params::{ - PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySURBEncryptionAlgorithm, + PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketMode, ReplySURBEncryptionAlgorithm, ReplySURBKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS, }; use nymsphinx_types::builder::SphinxPacketBuilder; -use nymsphinx_types::{delays, Delay, SphinxPacket}; +use nymsphinx_types::{delays, Delay}; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; use std::time::Duration; use topology::{NymTopology, NymTopologyError}; +mod vpn_manager; + /// Represents fully packed and prepared [`Fragment`] that can be sent through the mix network. pub struct PreparedFragment { /// Indicates the total expected round-trip time, i.e. delay from the sending of this message /// until receiving the acknowledgement included inside of it. pub total_delay: Delay, - /// Indicates address of the node to which the message should be sent. - pub first_hop_address: NymNodeRoutingAddress, - - /// The actual 'chunk' of the message that is going to go through the mix network. - pub sphinx_packet: SphinxPacket, + /// Indicates all data required to serialize and forward the data. It contains the actual + /// address of the node to which the message should be sent, the actual 'chunk' of the message + /// going through the mix network and also the 'mode' of the packet, i.e. VPN or Mix. + pub mix_packet: MixPacket, } #[derive(Debug)] @@ -64,7 +67,7 @@ impl From for PreparationError { /// Prepares the message that is to be sent through the mix network by attaching /// an optional reply-SURB, padding it to appropriate length, encrypting its content, /// and chunking into appropriate size [`Fragment`]s. -#[derive(Debug, Clone)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Clone))] pub struct MessagePreparer { /// Instance of a cryptographically secure random number generator. rng: R, @@ -85,6 +88,14 @@ pub struct MessagePreparer { /// Number of mix hops each packet ('real' message, ack, reply) is expected to take. /// Note that it does not include gateway hops. num_mix_hops: u8, + + /// Mode of all mix packets created - VPN or Mix. They indicate whether packets should get delayed + /// and keys reused. + mode: PacketMode, + + /// If the VPN mode is activated, this underlying secret will be used for multiple sphinx + /// packets created. + vpn_manager: Option, } impl MessagePreparer @@ -92,11 +103,22 @@ where R: CryptoRng + Rng, { pub fn new( - rng: R, + mut rng: R, sender_address: Recipient, average_packet_delay: Duration, average_ack_delay: Duration, + mode: PacketMode, + vpn_key_reuse_limit: Option, ) -> Self { + let vpn_manager = if mode.is_vpn() { + Some(VPNManager::new( + &mut rng, + vpn_key_reuse_limit.expect("No key reuse limit provided in vpn mode!"), + )) + } else { + None + }; + MessagePreparer { rng, packet_size: Default::default(), @@ -104,6 +126,8 @@ where average_packet_delay, average_ack_delay, num_mix_hops: DEFAULT_NUM_MIX_HOPS, + mode, + vpn_manager, } } @@ -218,7 +242,7 @@ where /// - compute vk_b = g^x || v_b /// - compute sphinx_plaintext = SURB_ACK || g^x || v_b /// - compute sphinx_packet = Sphinx(recipient, sphinx_plaintext) - pub fn prepare_chunk_for_sending( + pub async fn prepare_chunk_for_sending( &mut self, fragment: Fragment, topology: &NymTopology, @@ -227,9 +251,20 @@ where ) -> Result { // create an ack let (ack_delay, surb_ack_bytes) = self - .generate_surb_ack(fragment.fragment_identifier(), topology, ack_key)? + .generate_surb_ack(fragment.fragment_identifier(), topology, ack_key) + .await? .prepare_for_sending(); + // TODO: + // TODO: + // TODO: + // TODO: + // TODO: ASK @AP AND @DH WHETHER THOSE KEYS CAN/SHOULD ALSO BE REUSED IN VPN MODE!! + // TODO: + // TODO: + // TODO: + // TODO: + // create keys for 'payload' encryption let (ephemeral_keypair, shared_key) = new_ephemeral_shared_key::( @@ -269,40 +304,62 @@ where // create the actual sphinx packet here. With valid route and correct payload size, // there's absolutely no reason for this call to fail. - // note: once merged, that's an easy rng injection point for sphinx packets : ) - let sphinx_packet = SphinxPacketBuilder::new() - .with_payload_size(self.packet_size.payload_size()) - .build_packet(packet_payload, &route, &destination, &delays) - .unwrap(); + let sphinx_packet = if let Some(vpn_manager) = self.vpn_manager.as_mut() { + let initial_secret = vpn_manager.use_secret(&mut self.rng).await; + + SphinxPacketBuilder::new() + .with_payload_size(self.packet_size.payload_size()) + .with_initial_secret(&initial_secret) + .build_packet(packet_payload, &route, &destination, &delays) + .unwrap() + } else { + SphinxPacketBuilder::new() + .with_payload_size(self.packet_size.payload_size()) + .build_packet(packet_payload, &route, &destination, &delays) + .unwrap() + }; // from the previously constructed route extract the first hop let first_hop_address = - NymNodeRoutingAddress::try_from(route.first().unwrap().address.clone()).unwrap(); + NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap(); Ok(PreparedFragment { // the round-trip delay is the sum of delays of all hops on the forward route as // well as the total delay of the ack packet. total_delay: delays.iter().sum::() + ack_delay, - first_hop_address, - sphinx_packet, + mix_packet: MixPacket::new(first_hop_address, sphinx_packet, self.mode), }) } /// Construct an acknowledgement SURB for the given [`FragmentIdentifier`] - fn generate_surb_ack( + async fn generate_surb_ack( &mut self, fragment_id: FragmentIdentifier, topology: &NymTopology, ack_key: &AckKey, ) -> Result { - SURBAck::construct( - &mut self.rng, - &self.sender_address, - ack_key, - fragment_id.to_bytes(), - self.average_ack_delay, - topology, - ) + if let Some(vpn_manager) = self.vpn_manager.as_mut() { + let initial_secret = vpn_manager.use_secret(&mut self.rng).await; + SURBAck::construct( + &mut self.rng, + &self.sender_address, + ack_key, + fragment_id.to_bytes(), + self.average_ack_delay, + topology, + Some(&initial_secret), + ) + } else { + SURBAck::construct( + &mut self.rng, + &self.sender_address, + ack_key, + fragment_id.to_bytes(), + self.average_ack_delay, + topology, + None, + ) + } } /// Attaches an optional reply-surb and correct padding to the underlying message @@ -323,13 +380,13 @@ where } // TODO: perhaps the return type could somehow be combined with [`PreparedFragment`] ? - pub fn prepare_reply_for_use( + pub async fn prepare_reply_for_use( &mut self, message: Vec, reply_surb: ReplySURB, topology: &NymTopology, ack_key: &AckKey, - ) -> Result<(FragmentIdentifier, SphinxPacket, NymNodeRoutingAddress), PreparationError> { + ) -> Result<(MixPacket, FragmentIdentifier), PreparationError> { // there's no chunking in reply-surbs so there's a hard limit on message, // we also need to put the key digest into the message (same size as ephemeral key) // and need 1 byte to indicate padding length (this is not the case for 'normal' messages @@ -352,7 +409,8 @@ where // gateways could not distinguish reply packets from normal messages due to lack of said acks // note: the ack delay is irrelevant since we do not know the delay of actual surb let (_, surb_ack_bytes) = self - .generate_surb_ack(reply_id, topology, ack_key)? + .generate_surb_ack(reply_id, topology, ack_key) + .await? .prepare_for_sending(); let zero_pad_len = self.packet_size.plaintext_size() @@ -398,7 +456,7 @@ where .apply_surb(&packet_payload, Some(self.packet_size)) .unwrap(); - Ok((reply_id, packet, first_hop)) + Ok((MixPacket::new(first_hop, packet, self.mode), reply_id)) } #[allow(dead_code)] @@ -414,6 +472,8 @@ where average_packet_delay: Default::default(), average_ack_delay: Default::default(), num_mix_hops: DEFAULT_NUM_MIX_HOPS, + mode: Default::default(), + vpn_manager: None, } } } diff --git a/common/nymsphinx/src/preparer/vpn_manager.rs b/common/nymsphinx/src/preparer/vpn_manager.rs new file mode 100644 index 0000000000..dd589d38e4 --- /dev/null +++ b/common/nymsphinx/src/preparer/vpn_manager.rs @@ -0,0 +1,154 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use nymsphinx_types::EphemeralSecret; +use rand::{CryptoRng, Rng}; +use std::sync::atomic::{AtomicUsize, Ordering}; +#[cfg(not(target_arch = "wasm32"))] +use std::sync::Arc; +#[cfg(not(target_arch = "wasm32"))] +use tokio::sync::{RwLock, RwLockReadGuard}; + +#[cfg(not(target_arch = "wasm32"))] +pub(super) type SpinhxKeyRef<'a> = RwLockReadGuard<'a, EphemeralSecret>; + +#[cfg(target_arch = "wasm32")] +pub(super) type SpinhxKeyRef<'a> = &'a EphemeralSecret; + +#[cfg_attr(not(target_arch = "wasm32"), derive(Clone))] +pub(super) struct VPNManager { + #[cfg(not(target_arch = "wasm32"))] + inner: Arc, + + #[cfg(target_arch = "wasm32")] + inner: Inner, +} + +struct Inner { + /// Maximum number of times particular sphinx-secret can be re-used before being rotated. + secret_reuse_limit: usize, + + /// Currently used initial sphinx-secret for the packets sent. + #[cfg(not(target_arch = "wasm32"))] + current_initial_secret: RwLock, + + #[cfg(target_arch = "wasm32")] + // this is a temporary work-around for wasm (which currently does not have retransmission + // and hence will not require multi-thread access) and also we can't import tokio's RWLock + // in wasm. + current_initial_secret: EphemeralSecret, + + /// If the client is running as VPN it's expected to keep re-using the same initial secret + /// for a while so that the mixnodes could cache some secret derivation results. However, + /// we should reset it every once in a while. + packets_with_current_secret: AtomicUsize, +} + +impl VPNManager { + #[cfg(not(target_arch = "wasm32"))] + pub(super) fn new(mut rng: R, secret_reuse_limit: usize) -> Self + where + R: CryptoRng + Rng, + { + let initial_secret = EphemeralSecret::new_with_rng(&mut rng); + VPNManager { + inner: Arc::new(Inner { + secret_reuse_limit, + current_initial_secret: RwLock::new(initial_secret), + packets_with_current_secret: AtomicUsize::new(0), + }), + } + } + + #[cfg(target_arch = "wasm32")] + pub(super) fn new(mut rng: R, secret_reuse_limit: usize) -> Self + where + R: CryptoRng + Rng, + { + let initial_secret = EphemeralSecret::new_with_rng(&mut rng); + VPNManager { + inner: Inner { + secret_reuse_limit, + current_initial_secret: initial_secret, + packets_with_current_secret: AtomicUsize::new(0), + }, + } + } + + #[cfg(not(target_arch = "wasm32"))] + pub(super) async fn rotate_secret(&mut self, mut rng: R) + where + R: CryptoRng + Rng, + { + let new_secret = EphemeralSecret::new_with_rng(&mut rng); + let mut write_guard = self.inner.current_initial_secret.write().await; + + *write_guard = new_secret; + // in here we have an exclusive lock so we don't have to have restrictive ordering as no + // other thread will be able to get here + self.inner + .packets_with_current_secret + .store(0, Ordering::Relaxed) + } + + // this method is async for consistency with non-wasm version + #[cfg(target_arch = "wasm32")] + pub(super) async fn rotate_secret(&mut self, mut rng: R) + where + R: CryptoRng + Rng, + { + let new_secret = EphemeralSecret::new_with_rng(&mut rng); + self.inner.current_initial_secret = new_secret; + + // wasm is single-threaded so relaxed ordering is also fine here + self.inner + .packets_with_current_secret + .store(0, Ordering::Relaxed); + } + + #[cfg(not(target_arch = "wasm32"))] + pub(super) async fn current_secret(&self) -> SpinhxKeyRef<'_> { + self.inner.current_initial_secret.read().await + } + + #[cfg(target_arch = "wasm32")] + pub(super) async fn current_secret(&self) -> SpinhxKeyRef<'_> { + &self.inner.current_initial_secret + } + + fn increment_key_usage(&mut self) { + // TODO: is this the appropriate ordering? + self.inner + .packets_with_current_secret + .fetch_add(1, Ordering::SeqCst); + } + + fn current_key_usage(&self) -> usize { + // TODO: is this the appropriate ordering? + self.inner + .packets_with_current_secret + .load(Ordering::SeqCst) + } + + pub(super) async fn use_secret<'a, R>(&'a mut self, rng: R) -> SpinhxKeyRef<'a> + where + R: CryptoRng + Rng, + { + if self.current_key_usage() > self.inner.secret_reuse_limit { + self.rotate_secret(rng).await; + } + self.increment_key_usage(); + self.current_secret().await + } +} diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index 8481af1837..48c167ce25 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -34,7 +34,7 @@ pub struct ReconstructedMessage { pub enum MessageRecoveryError { InvalidSurbPrefixError, MalformedSURBError(ReplySURBError), - InvalidRemoteEphemeralKey(encryption::EncryptionKeyError), + InvalidRemoteEphemeralKey(encryption::KeyRecoveryError), MalformedFragmentError, InvalidMessagePaddingError, MalformedReconstructedMessage(Vec), @@ -47,8 +47,8 @@ impl From for MessageRecoveryError { } } -impl From for MessageRecoveryError { - fn from(err: encryption::EncryptionKeyError) -> Self { +impl From for MessageRecoveryError { + fn from(err: encryption::KeyRecoveryError) -> Self { MessageRecoveryError::InvalidRemoteEphemeralKey(err) } } @@ -216,13 +216,18 @@ mod message_receiver { vec![mix::Node { location: "unknown".to_string(), host: "10.20.30.40:1789".parse().unwrap(), - pub_key: encryption::PublicKey::from_base58_string( + identity_key: identity::PublicKey::from_base58_string( + "3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7", + ) + .unwrap(), + sphinx_key: encryption::PublicKey::from_base58_string( "B3GzG62aXAZNg14RoMCp3BhELNBrySLr2JqrwyfYFzRc", ) .unwrap(), layer: 1, - last_seen: 1594812897745695000, + registration_time: 1594812897745695000, version: "0.8.0-dev".to_string(), + reputation: 100, }], ); @@ -231,13 +236,18 @@ mod message_receiver { vec![mix::Node { location: "unknown".to_string(), host: "11.21.31.41:1789".parse().unwrap(), - pub_key: encryption::PublicKey::from_base58_string( + identity_key: identity::PublicKey::from_base58_string( + "D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr", + ) + .unwrap(), + sphinx_key: encryption::PublicKey::from_base58_string( "5Z1VqYwM2xeKxd8H7fJpGWasNiDFijYBAee7MErkZ5QT", ) .unwrap(), layer: 2, - last_seen: 1594812897745695000, + registration_time: 1594812897745695000, version: "0.8.0-dev".to_string(), + reputation: 100, }], ); @@ -246,19 +256,23 @@ mod message_receiver { vec![mix::Node { location: "unknown".to_string(), host: "12.22.32.42:1789".parse().unwrap(), - pub_key: encryption::PublicKey::from_base58_string( + identity_key: identity::PublicKey::from_base58_string( + "GkWDysw4AjESv1KiAiVn7JzzCMJeksxNSXVfr1PpX8wD", + ) + .unwrap(), + sphinx_key: encryption::PublicKey::from_base58_string( "9EyjhCggr2QEA2nakR88YHmXgpy92DWxoe2draDRkYof", ) .unwrap(), layer: 3, - last_seen: 1594812897745695000, + registration_time: 1594812897745695000, version: "0.8.0-dev".to_string(), + reputation: 100, }], ); NymTopology::new( // currently coco_nodes don't really exist so this is still to be determined - vec![], mixes, vec![gateway::Node { location: "unknown".to_string(), @@ -272,7 +286,8 @@ mod message_receiver { "EB42xvMFMD5rUCstE2CDazgQQJ22zLv8SPm1Luxni44c", ) .unwrap(), - last_seen: 1594812897745695000, + registration_time: 1594812897745695000, + reputation: 100, version: "0.8.0-dev".to_string(), }], ) diff --git a/common/nymsphinx/types/Cargo.toml b/common/nymsphinx/types/Cargo.toml index 5fe2e758a8..ac00a39fa5 100644 --- a/common/nymsphinx/types/Cargo.toml +++ b/common/nymsphinx/types/Cargo.toml @@ -7,5 +7,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -sphinx = { git = "https://github.com/nymtech/sphinx", rev="18aa34e1a39a5f3f14ba493ded9209658ff2cbfa" } +sphinx = { git = "https://github.com/nymtech/sphinx", rev="283dcc77dec8ee9ed3bed58c2b878e9c18320723" } #sphinx = { path = "../../../../sphinx"} \ No newline at end of file diff --git a/common/socks5/ordered-buffer/src/buffer.rs b/common/socks5/ordered-buffer/src/buffer.rs index ca26e35ccf..4034b2ca72 100644 --- a/common/socks5/ordered-buffer/src/buffer.rs +++ b/common/socks5/ordered-buffer/src/buffer.rs @@ -50,13 +50,10 @@ impl OrderedMessageBuffer { let mut contiguous_messages = Vec::new(); let mut index = self.next_index; - loop { - if let Some(ordered_message) = self.messages.remove(&index) { - contiguous_messages.push(ordered_message); - index += 1; - } else { - break; - } + + while let Some(ordered_message) = self.messages.remove(&index) { + contiguous_messages.push(ordered_message); + index += 1; } let high_water = index; diff --git a/common/socks5/ordered-buffer/src/message.rs b/common/socks5/ordered-buffer/src/message.rs index 6bb47847e6..27b64f45c9 100644 --- a/common/socks5/ordered-buffer/src/message.rs +++ b/common/socks5/ordered-buffer/src/message.rs @@ -27,7 +27,7 @@ impl OrderedMessage { /// Attempts to deserialize an `OrderedMessage` from bytes. pub fn try_from_bytes(data: Vec) -> Result { - if data.len() == 0 { + if data.is_empty() { return Err(MessageError::NoData); } diff --git a/common/socks5/ordered-buffer/src/sender.rs b/common/socks5/ordered-buffer/src/sender.rs index fde13d9a17..c4c16e03a5 100644 --- a/common/socks5/ordered-buffer/src/sender.rs +++ b/common/socks5/ordered-buffer/src/sender.rs @@ -19,7 +19,7 @@ impl OrderedMessageSender { data: input.to_vec(), index: self.next_index, }; - self.next_index = self.next_index + 1; + self.next_index += 1; message } } diff --git a/common/socks5/proxy-helpers/src/available_reader.rs b/common/socks5/proxy-helpers/src/available_reader.rs index a08ca01e1d..2fed57602e 100644 --- a/common/socks5/proxy-helpers/src/available_reader.rs +++ b/common/socks5/proxy-helpers/src/available_reader.rs @@ -20,13 +20,18 @@ use std::ops::DerefMut; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::AsyncRead; +use tokio::stream::Stream; +use tokio::time::{delay_for, Delay, Duration, Instant}; + +const MAX_READ_AMOUNT: usize = 500 * 1000; // 0.5MB +const GRACE_DURATION: Duration = Duration::from_millis(1); pub struct AvailableReader<'a, R: AsyncRead + Unpin> { - // TODO: come up with a way to avoid using RefCell (not sure if possible though) + // TODO: come up with a way to avoid using RefCell (not sure if possible though due to having to + // mutably borrow both inner reader and buffer at the same time) buf: RefCell, inner: RefCell<&'a mut R>, - // idea for the future: tiny delay that allows to prevent unnecessary extra fragmentation - // grace_period: Option, + grace_period: Option, } impl<'a, R> AvailableReader<'a, R> @@ -39,20 +44,15 @@ where AvailableReader { buf: RefCell::new(BytesMut::with_capacity(Self::BUF_INCREMENT)), inner: RefCell::new(reader), - // grace_period: None, + grace_period: Some(delay_for(GRACE_DURATION)), } } } -// TODO: change this guy to a stream? Seems waaay more appropriate considering -// we're getting new Bytes items regularly rather than calling it once. +impl<'a, R: AsyncRead + Unpin> Stream for AvailableReader<'a, R> { + type Item = io::Result; -impl<'a, R: AsyncRead + Unpin> Future for AvailableReader<'a, R> { - type Output = io::Result<(Bytes, bool)>; - - // this SHOULD stay mutable, because we rely on runtime checks inside the method - #[allow(unused_mut)] - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // if we have no space in buffer left - expand it if !self.buf.borrow().has_remaining_mut() { self.buf.borrow_mut().reserve(Self::BUF_INCREMENT); @@ -68,19 +68,43 @@ impl<'a, R: AsyncRead + Unpin> Future for AvailableReader<'a, R> { if self.buf.borrow().is_empty() { Poll::Pending } else { + // if exists - check grace period + if let Some(grace_period) = self.grace_period.as_mut() { + if Pin::new(grace_period).poll(cx).is_pending() { + return Poll::Pending; + } + } + let buf = self.buf.replace(BytesMut::new()); - Poll::Ready(Ok((buf.freeze(), false))) + Poll::Ready(Some(Ok(buf.freeze()))) } } - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Err(err)) => Poll::Ready(Some(Err(err))), Poll::Ready(Ok(n)) => { + // if exists - reset grace period + if let Some(grace_period) = self.grace_period.as_mut() { + let now = Instant::now(); + grace_period.reset(now + GRACE_DURATION); + } + // if we read a non-0 amount, we're not done yet! if n == 0 { let buf = self.buf.replace(BytesMut::new()); - Poll::Ready(Ok((buf.freeze(), true))) + if buf.len() > 0 { + Poll::Ready(Some(Ok(buf.freeze()))) + } else { + Poll::Ready(None) + } } else { // tell the waker we should be polled again! cx.waker().wake_by_ref(); + + // if we reached our maximum amount - return it + let read_bytes_len = self.buf.borrow().len(); + if read_bytes_len >= MAX_READ_AMOUNT { + let buf = self.buf.replace(BytesMut::new()); + return Poll::Ready(Some(Ok(buf.freeze()))); + } Poll::Pending } } @@ -91,19 +115,22 @@ impl<'a, R: AsyncRead + Unpin> Future for AvailableReader<'a, R> { #[cfg(test)] mod tests { use super::*; + use futures::poll; use std::io::Cursor; use std::time::Duration; + use tokio::stream::StreamExt; + use tokio_test::assert_pending; #[tokio::test] async fn available_reader_reads_all_available_data_smaller_than_its_buf() { let data = vec![42u8; 100]; let mut reader = Cursor::new(data.clone()); - let available_reader = AvailableReader::new(&mut reader); - let (read_data, is_finished) = available_reader.await.unwrap(); + let mut available_reader = AvailableReader::new(&mut reader); + let read_data = available_reader.next().await.unwrap().unwrap(); assert_eq!(read_data, data); - assert!(is_finished) + assert!(available_reader.next().await.is_none()) } #[tokio::test] @@ -111,11 +138,11 @@ mod tests { let data = vec![42u8; AvailableReader::>>::BUF_INCREMENT + 100]; let mut reader = Cursor::new(data.clone()); - let available_reader = AvailableReader::new(&mut reader); - let (read_data, is_finished) = available_reader.await.unwrap(); + let mut available_reader = AvailableReader::new(&mut reader); + let read_data = available_reader.next().await.unwrap().unwrap(); assert_eq!(read_data, data); - assert!(is_finished) + assert!(available_reader.next().await.is_none()) } #[tokio::test] @@ -129,11 +156,11 @@ mod tests { .read(&second_data_chunk) .build(); - let available_reader = AvailableReader::new(&mut reader_mock); - let (read_data, is_finished) = available_reader.await.unwrap(); + let mut available_reader = AvailableReader::new(&mut reader_mock); + let read_data = available_reader.next().await.unwrap().unwrap(); assert_eq!(read_data, first_data_chunk); - assert!(!is_finished) + assert_pending!(poll!(available_reader.next())); } #[tokio::test] @@ -145,10 +172,40 @@ mod tests { .read(&data) .build(); - let available_reader = AvailableReader::new(&mut reader_mock); - let (read_data, is_finished) = available_reader.await.unwrap(); + let mut available_reader = AvailableReader::new(&mut reader_mock); + let read_data = available_reader.next().await.unwrap().unwrap(); assert_eq!(read_data, data); - assert!(is_finished) + assert!(available_reader.next().await.is_none()) } + + // perhaps the issue of tokio io builder will be resolved in tokio 0.3? + // #[tokio::test] + // async fn available_reader_will_wait_for_more_data_if_its_within_grace_period() { + // let first_data_chunk = vec![42u8; 100]; + // let second_data_chunk = vec![123u8; 100]; + // + // let combined_chunks: Vec<_> = first_data_chunk + // .iter() + // .cloned() + // .chain(second_data_chunk.iter().cloned()) + // .collect(); + // + // let mut reader_mock = tokio_test::io::Builder::new() + // .read(&first_data_chunk) + // .wait(Duration::from_millis(2)) + // .read(&second_data_chunk) + // .build(); + // + // let mut available_reader = AvailableReader { + // buf: RefCell::new(BytesMut::with_capacity(4096)), + // inner: RefCell::new(&mut reader_mock), + // grace_period: Some(delay_for(Duration::from_millis(5))), + // }; + // + // let read_data = available_reader.next().await.unwrap().unwrap(); + // + // assert_eq!(read_data, combined_chunks); + // assert!(available_reader.next().await.is_none()) + // } } diff --git a/common/socks5/proxy-helpers/src/connection_controller.rs b/common/socks5/proxy-helpers/src/connection_controller.rs index 558197158e..eaf2cada75 100644 --- a/common/socks5/proxy-helpers/src/connection_controller.rs +++ b/common/socks5/proxy-helpers/src/connection_controller.rs @@ -41,7 +41,7 @@ pub type ControllerSender = mpsc::UnboundedSender; pub type ControllerReceiver = mpsc::UnboundedReceiver; pub enum ControllerCommand { - Insert(ConnectionId, ConnectionSender, OrderedMessageBuffer), + Insert(ConnectionId, ConnectionSender), Remove(ConnectionId), Send(ConnectionId, Vec, bool), } @@ -75,7 +75,16 @@ pub struct Controller { active_connections: HashMap, receiver: ControllerReceiver, + // TODO: this will need to be either completely removed (from code) or periodically cleaned + // to avoid memory issues recently_closed: HashSet, + + // TODO: this can potentially be abused to ddos and kill provider. Not sure at this point + // how to handle it more gracefully + + // buffer for messages received before connection was established due to mixnet being able to + // un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here + pending_messages: HashMap, bool)>>, } impl Controller { @@ -86,27 +95,28 @@ impl Controller { active_connections: HashMap::new(), receiver, recently_closed: HashSet::new(), + pending_messages: HashMap::new(), }, sender, ) } - fn insert_connection( - &mut self, - conn_id: ConnectionId, - connection_sender: ConnectionSender, - ordered_buffer: OrderedMessageBuffer, - ) { + fn insert_connection(&mut self, conn_id: ConnectionId, connection_sender: ConnectionSender) { let active_connection = ActiveConnection { is_closed: false, connection_sender: Some(connection_sender), - ordered_buffer, + ordered_buffer: OrderedMessageBuffer::new(), }; if let Some(_active_conn) = self.active_connections.insert(conn_id, active_connection) { - // we received 'Send' before 'connect' - drain what we currently accumulated into the fresh - // buffer as this new one is going to be used for the connection - // TODO: let's only do this if it's actually EVER fired - error!("Presumably received 'Send' before 'Connect'!") + error!("Received a duplicate 'Connect'!") + } else { + // check if there were any pending messages + if let Some(pending) = self.pending_messages.remove(&conn_id) { + debug!("There were some pending messages for {}", conn_id); + for (payload, is_closed) in pending { + self.send_to_connection(conn_id, payload, is_closed) + } + } } } @@ -129,7 +139,7 @@ impl Controller { active_connection.is_closed |= is_closed; if let Some(payload) = active_connection.read_from_buf() { - active_connection + if let Err(err) = active_connection .connection_sender .as_mut() .unwrap() @@ -137,14 +147,28 @@ impl Controller { payload, socket_closed: active_connection.is_closed, }) - .unwrap() + { + error!("WTF IS THIS: {:?}", err); + } + + // TODO: ABOVE UNWRAP CAUSED A CRASH IN A NORMAL USE!!!! + // TODO: + // TODO: surprisingly it only happened on socks client, never on nSP + // TODO: + // TODO: + // TODO: + // TODO: } } else { - error!("no connection exists with id: {:?}", conn_id); - warn!("'lost' bytes: {}", payload.len()); if !self.recently_closed.contains(&conn_id) { - // TODO: let's only do this if it's actually EVER fired - error!("Presumably received 'Send' before 'Connect'! - First") + warn!("Received a 'Send' before 'Connect' - going to buffer the data"); + let pending = self.pending_messages.entry(conn_id).or_insert(Vec::new()); + pending.push((payload, is_closed)); + } else { + error!( + "Tried to write to closed connection ({} bytes were 'lost)", + payload.len() + ) } } } @@ -155,8 +179,8 @@ impl Controller { ControllerCommand::Send(conn_id, data, is_closed) => { self.send_to_connection(conn_id, data, is_closed) } - ControllerCommand::Insert(conn_id, sender, ordered_buffer) => { - self.insert_connection(conn_id, sender, ordered_buffer) + ControllerCommand::Insert(conn_id, sender) => { + self.insert_connection(conn_id, sender) } ControllerCommand::Remove(conn_id) => self.remove_connection(conn_id), } diff --git a/common/socks5/proxy-helpers/src/lib.rs b/common/socks5/proxy-helpers/src/lib.rs index 2ab7488611..0141322047 100644 --- a/common/socks5/proxy-helpers/src/lib.rs +++ b/common/socks5/proxy-helpers/src/lib.rs @@ -15,4 +15,3 @@ pub mod available_reader; pub mod connection_controller; pub mod proxy_runner; -// pub mod read_delay_loop; diff --git a/common/socks5/proxy-helpers/src/proxy_runner.rs b/common/socks5/proxy-helpers/src/proxy_runner.rs index f4a583cb91..c951eaf299 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner.rs @@ -18,12 +18,10 @@ use futures::channel::mpsc; use log::*; use ordered_buffer::OrderedMessageSender; use socks5_requests::ConnectionId; -use std::sync::Arc; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::TcpStream; use tokio::prelude::*; use tokio::stream::StreamExt; -use tokio::sync::Notify; #[derive(Debug)] pub struct ProxyMessage { @@ -51,10 +49,9 @@ pub struct ProxyRunner { mix_sender: MixProxySender, socket: Option, + local_destination_address: String, + remote_source_address: String, connection_id: ConnectionId, - - // required for in-order delivery - message_sender: Option, } impl ProxyRunner @@ -63,118 +60,107 @@ where { pub fn new( socket: TcpStream, + local_destination_address: String, // addresses are provided for better logging + remote_source_address: String, mix_receiver: ConnectionReceiver, mix_sender: MixProxySender, connection_id: ConnectionId, - message_sender: OrderedMessageSender, ) -> Self { ProxyRunner { mix_receiver: Some(mix_receiver), mix_sender, socket: Some(socket), + local_destination_address, + remote_source_address, connection_id, - message_sender: Some(message_sender), } } async fn run_inbound( mut reader: OwnedReadHalf, - notify_closed: Arc, + local_destination_address: String, // addresses are provided for better logging + remote_source_address: String, connection_id: ConnectionId, mix_sender: MixProxySender, adapter_fn: F, - mut message_sender: OrderedMessageSender, - ) -> (OwnedReadHalf, OrderedMessageSender) + ) -> OwnedReadHalf where F: Fn(ConnectionId, Vec, bool) -> S + Send + 'static, { let mut available_reader = AvailableReader::new(&mut reader); + let mut message_sender = OrderedMessageSender::new(); loop { - tokio::select! { - _ = notify_closed.notified() => { - // the remote socket is closed, so there's no point - // in reading anything more because we won't be able to write to remote anyway! - break - } - // try to read from local socket and push everything to mixnet to the remote - reading_result = &mut available_reader => { - let (read_data, is_finished) = match reading_result { - Ok(data) => data, - Err(err) => { - error!("failed to read request from the socket - {}", err); - break; - } - }; - - info!( - "Going to send {} bytes via mixnet to remote {}. Is local closed: {}", - read_data.len(), - connection_id, - is_finished - ); - - // if we're sending through the mixnet increase the sequence number... - let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes(); - mix_sender.unbounded_send(adapter_fn(connection_id, ordered_msg, is_finished)).unwrap(); - - if is_finished { - // technically we already informed it when we sent the message to mixnet above - info!("The local socket is closed - won't receive any more data. Informing remote about that..."); - // no point in reading from mixnet if connection is closed! - notify_closed.notify(); + // try to read from local socket and push everything to mixnet to the remote + let (read_data, is_finished) = match available_reader.next().await { + Some(data) => match data { + Ok(data) => (data, false), + Err(err) => { + error!(target: &*format!("({}) socks5 inbound", connection_id),"failed to read request from the socket - {}", err); break; - } else { - // delay_for(Duration::from_millis(2)).await; } - } + }, + None => (Default::default(), true), + }; + + debug!( + target: &*format!("({}) socks5 inbound", connection_id), + "[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}", + read_data.len(), + local_destination_address, + remote_source_address, + is_finished + ); + + // if we're sending through the mixnet increase the sequence number... + let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes(); + mix_sender + .unbounded_send(adapter_fn(connection_id, ordered_msg, is_finished)) + .unwrap(); + + if is_finished { + // technically we already informed it when we sent the message to mixnet above + debug!(target: &*format!("({}) socks5 inbound", connection_id), "The local socket is closed - won't receive any more data. Informing remote about that..."); + break; } } - (reader, message_sender) + reader } async fn run_outbound( mut writer: OwnedWriteHalf, - notify_closed: Arc, + local_destination_address: String, // addresses are provided for better logging + remote_source_address: String, mut mix_receiver: ConnectionReceiver, connection_id: ConnectionId, ) -> (OwnedWriteHalf, ConnectionReceiver) { loop { - tokio::select! { - _ = notify_closed.notified() => { - // no need to read from mixnet as we won't be able to send to socket - // anyway - break - } - mix_data = mix_receiver.next() => { - if mix_data.is_none() { - warn!("mix receiver is none so we already got removed somewhere. This isn't really a warning, but shouldn't happen to begin with, so please say if you see this message"); - // we already got closed - // not sure if we HAVE TO notify the other task, but might as well - notify_closed.notify(); - break - } - let connection_message = mix_data.unwrap(); + let mix_data = mix_receiver.next().await; + if mix_data.is_none() { + warn!("mix receiver is none so we already got removed somewhere. This isn't really a warning, but shouldn't happen to begin with, so please say if you see this message"); + break; + } + let connection_message = mix_data.unwrap(); - info!( - "Going to write {} bytes received from mixnet to connection {}. Is remote closed: {}", - connection_message.payload.len(), - connection_id, - connection_message.socket_closed - ); + debug!( + target: &*format!("({}) socks5 outbound", connection_id), + "[{} bytes]\t{} → remote → mixnet → local → {} Remote closed: {}", + connection_message.payload.len(), + remote_source_address, + local_destination_address, + connection_message.socket_closed + ); - if let Err(err) = writer.write_all(&connection_message.payload).await { - // the other half is probably going to blow up too (if not, this task also needs to notify the other one!!) - error!("failed to write response back to the socket - {}", err); - break; - } - if connection_message.socket_closed { - info!("Remote socket got closed - closing the local socket too"); - notify_closed.notify(); - break - } - } + if let Err(err) = writer.write_all(&connection_message.payload).await { + // the other half is probably going to blow up too (if not, this task also needs to notify the other one!!) + error!(target: &*format!("({}) socks5 outbound", connection_id), "failed to write response back to the socket - {}", err); + break; + } + if connection_message.socket_closed { + debug!(target: &*format!("({}) socks5 outbound", connection_id), + "Remote socket got closed - closing the local socket too"); + break; } } @@ -187,24 +173,22 @@ where where F: Fn(ConnectionId, Vec, bool) -> S + Send + 'static, { - let notify_closed = Arc::new(Notify::new()); - let notify_clone = Arc::clone(¬ify_closed); - let (read_half, write_half) = self.socket.take().unwrap().into_split(); // should run until either inbound closes or is notified from outbound let inbound_future = Self::run_inbound( read_half, - notify_closed, + self.local_destination_address.clone(), + self.remote_source_address.clone(), self.connection_id, self.mix_sender.clone(), adapter_fn, - self.message_sender.take().unwrap(), ); let outbound_future = Self::run_outbound( write_half, - notify_clone, + self.local_destination_address.clone(), + self.remote_source_address.clone(), self.mix_receiver.take().unwrap(), self.connection_id, ); @@ -222,12 +206,11 @@ where panic!("TODO: some future error?") } - let (read_half, message_sender) = inbound_result.unwrap(); + let read_half = inbound_result.unwrap(); let (write_half, mix_receiver) = outbound_result.unwrap(); self.socket = Some(write_half.reunite(read_half).unwrap()); self.mix_receiver = Some(mix_receiver); - self.message_sender = Some(message_sender); self } diff --git a/common/socks5/proxy-helpers/src/read_delay_loop.rs b/common/socks5/proxy-helpers/src/read_delay_loop.rs deleted file mode 100644 index 647cf2a2a8..0000000000 --- a/common/socks5/proxy-helpers/src/read_delay_loop.rs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The only reason this exists is to remove duplicate code from -// nym\service-providers\simple-socks5\src\connection.rs::try_read_response_data -// and -// nym\clients\socks5\src\socks\request.rs::try_read_request_data - -// once those use sequence numbers, this code should be removed!! - -use crate::available_reader::AvailableReader; -use std::io; -use tokio::io::AsyncRead; -use tokio::time::Duration; - -// It returns data alognside information whether it timed out while reading from the socket -pub async fn try_read_data( - timeout: Duration, - mut reader: R, - address: &str, -) -> io::Result<(Vec, bool)> -where - R: AsyncRead + Unpin, -{ - let mut data = Vec::new(); - let mut delay = tokio::time::delay_for(timeout); - - let mut available_reader = AvailableReader::new(&mut reader); - - loop { - tokio::select! { - _ = &mut delay => { - if data.len() > 0 { - println!("Timed out. returning {} bytes received from {}", data.len(), address); - } - return Ok((data, true)) // we return all response data on timeout - } - read_data = &mut available_reader => { - match read_data { - Err(err) => { - return Err(err); - } - Ok(bytes) => { - if bytes.len() == 0 { - println!("Connection is closed! Returning {} bytes received from {}", data.len(), address); - // we return all we managed to read because - // we know no more stuff is coming - return Ok((data, false)) - } - let now = tokio::time::Instant::now(); - let next = now + timeout; - delay.reset(next); - println!("Received {} bytes from {}. Waiting for more...", bytes.len(), address); - - // temporarily this is fine... (this loop will go away anyway) - data.extend_from_slice(&bytes) - } - } - } - } - } -} diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index c4fe1979cc..d865d8c2a8 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -1,5 +1,4 @@ use nymsphinx_addressing::clients::{Recipient, RecipientFormattingError}; -use ordered_buffer::OrderedMessage; use std::convert::TryFrom; use std::fmt::{self}; @@ -28,7 +27,7 @@ impl fmt::Display for RequestError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { match self { RequestError::AddressLengthTooShort => { - write!(f, "not enough bytes to recover the lenght of the address") + write!(f, "not enough bytes to recover the length of the address") } RequestError::AddressTooShort => write!(f, "not enough bytes to recover the address"), RequestError::ConnectionIdTooShort => { @@ -48,10 +47,7 @@ impl std::error::Error for RequestError {} impl RequestError { pub fn is_malformed_return(&self) -> bool { - match self { - RequestError::MalformedReturnAddress(_) => true, - _ => false, - } + matches!(self, RequestError::MalformedReturnAddress(_)) } } @@ -77,7 +73,6 @@ pub enum Request { Connect { conn_id: ConnectionId, remote_addr: RemoteAddress, - message: OrderedMessage, return_address: Recipient, }, @@ -90,13 +85,11 @@ impl Request { pub fn new_connect( conn_id: ConnectionId, remote_addr: RemoteAddress, - message: OrderedMessage, return_address: Recipient, ) -> Request { Request::Connect { conn_id, remote_addr, - message, return_address, } } @@ -153,23 +146,19 @@ impl Request { // just a temporary reference to mid-slice for ease of use let recipient_data_bytes = &connect_request_bytes[address_end..]; - if recipient_data_bytes.len() < Recipient::LEN { + + if recipient_data_bytes.len() != Recipient::LEN { return Err(RequestError::ReturnAddressTooShort); } let mut return_bytes = [0u8; Recipient::LEN]; return_bytes.copy_from_slice(&recipient_data_bytes[..Recipient::LEN]); let return_address = Recipient::try_from_bytes(return_bytes) - .map_err(|err| RequestError::MalformedReturnAddress(err))?; - - let message = - OrderedMessage::try_from_bytes(recipient_data_bytes[Recipient::LEN..].to_vec()) - .unwrap(); + .map_err(RequestError::MalformedReturnAddress)?; Ok(Request::Connect { conn_id: connection_id, remote_addr: remote_address, - message, return_address, }) } @@ -187,11 +176,10 @@ impl Request { /// service provider which will make the request. pub fn into_bytes(self) -> Vec { match self { - // connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN || DATA + // connect is: CONN_FLAG || CONN_ID || REMOTE_LEN || REMOTE || RETURN Request::Connect { conn_id, remote_addr, - message, return_address, } => { let remote_address_bytes = remote_addr.into_bytes(); @@ -202,7 +190,6 @@ impl Request { .chain(remote_address_bytes_len.to_be_bytes().iter().cloned()) .chain(remote_address_bytes.into_iter()) .chain(return_address.to_bytes().iter().cloned()) - .chain(message.into_bytes()) .collect() } Request::Send(conn_id, data, local_closed) => std::iter::once(RequestFlag::Send as u8) @@ -382,7 +369,6 @@ mod request_deserialization_tests { let request_bytes: Vec<_> = request_bytes .into_iter() .chain(recipient_bytes.iter().cloned()) - .chain(vec![0, 0, 0, 0, 0, 0, 0, 1]) // message index 1 .collect(); let request = Request::try_from_bytes(&request_bytes).unwrap(); @@ -390,7 +376,6 @@ mod request_deserialization_tests { Request::Connect { conn_id, remote_addr, - message, return_address, } => { assert_eq!("foo.com".to_string(), remote_addr); @@ -399,7 +384,6 @@ mod request_deserialization_tests { return_address.to_bytes().to_vec(), recipient.to_bytes().to_vec() ); - assert_eq!(Vec::::new(), message.data); } _ => unreachable!(), } @@ -436,8 +420,6 @@ mod request_deserialization_tests { let request_bytes: Vec<_> = request_bytes .into_iter() .chain(recipient_bytes.iter().cloned()) - .chain(vec![0, 0, 0, 0, 0, 0, 0, 1]) // ordered message sequence number 1 - .chain(vec![255, 255, 255].into_iter()) .collect(); let request = Request::try_from_bytes(&request_bytes).unwrap(); @@ -445,7 +427,6 @@ mod request_deserialization_tests { Request::Connect { conn_id, remote_addr, - message, return_address, } => { assert_eq!("foo.com".to_string(), remote_addr); @@ -454,8 +435,6 @@ mod request_deserialization_tests { return_address.to_bytes().to_vec(), recipient.to_bytes().to_vec() ); - assert_eq!(1, message.index); - assert_eq!(vec![255, 255, 255], message.data); } _ => unreachable!(), } diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index 31a86d768e..39300ddef6 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -19,11 +19,6 @@ use nymsphinx_types::Node as SphinxNode; use std::convert::TryInto; use std::net::SocketAddr; -#[derive(Debug, Clone)] -pub struct Client { - pub pub_key: String, -} - #[derive(Debug, Clone)] pub struct Node { pub location: String, @@ -31,7 +26,8 @@ pub struct Node { pub mixnet_listener: SocketAddr, pub identity_key: identity::PublicKey, pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519 - pub last_seen: u64, + pub registration_time: i64, + pub reputation: i64, pub version: String, } diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index def926f96f..386fef0fc0 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -18,7 +18,6 @@ use nymsphinx_types::Node as SphinxNode; use rand::Rng; use std::collections::HashMap; -pub mod coco; mod filter; pub mod gateway; pub mod mix; @@ -35,34 +34,35 @@ pub enum NymTopologyError { pub type MixLayer = u8; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NymTopology { - coco_nodes: Vec, mixes: HashMap>, gateways: Vec, } impl NymTopology { - pub fn new( - coco_nodes: Vec, - mixes: HashMap>, - gateways: Vec, - ) -> Self { - NymTopology { - coco_nodes, - mixes, - gateways, - } - } - - pub fn coco_nodes(&self) -> &Vec { - &self.coco_nodes + pub fn new(mixes: HashMap>, gateways: Vec) -> Self { + NymTopology { mixes, gateways } } pub fn mixes(&self) -> &HashMap> { &self.mixes } + pub fn mixes_as_vec(&self) -> Vec { + let mut mixes: Vec = vec![]; + + for layer in self.mixes().values() { + mixes.extend(layer.to_owned()) + } + mixes + } + + pub fn mixes_in_layer(&self, layer: u8) -> Vec { + assert!(vec![1, 2, 3].contains(&layer)); + self.mixes.get(&layer).unwrap().to_owned() + } + pub fn gateways(&self) -> &Vec { &self.gateways } @@ -134,6 +134,10 @@ impl NymTopology { .collect()) } + pub fn set_mixes_in_layer(&mut self, layer: u8, mixes: Vec) { + self.mixes.insert(layer, mixes); + } + pub fn can_construct_path_through(&self, num_mix_hops: u8) -> bool { // if there are no gateways present, we can't do anything if self.gateways.is_empty() { @@ -160,19 +164,83 @@ impl NymTopology { } pub fn filter_system_version(&self, expected_version: &str) -> Self { - self.filter_node_versions(expected_version, expected_version, expected_version) + self.filter_node_versions(expected_version, expected_version) } pub fn filter_node_versions( &self, expected_mix_version: &str, expected_gateway_version: &str, - expected_coco_version: &str, ) -> Self { NymTopology { mixes: self.mixes.filter_by_version(expected_mix_version), gateways: self.gateways.filter_by_version(expected_gateway_version), - coco_nodes: self.coco_nodes.filter_by_version(expected_coco_version), + } + } +} + +#[cfg(test)] +mod converting_mixes_to_vec { + use super::*; + + #[cfg(test)] + mod when_nodes_exist { + use crypto::asymmetric::{encryption, identity}; + + use super::*; + + #[test] + fn returns_a_vec_with_hashmap_values() { + let node1 = mix::Node { + location: "London".to_string(), + host: "3.3.3.3:1789".parse().unwrap(), + identity_key: identity::PublicKey::from_base58_string( + "3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7", + ) + .unwrap(), + sphinx_key: encryption::PublicKey::from_base58_string( + "C7cown6dYCLZpLiMFC1PaBmhvLvmJmLDJGeRTbPD45bX", + ) + .unwrap(), + layer: 1, + registration_time: 123, + reputation: 0, + version: "0.x.0".to_string(), + }; + + let node2 = mix::Node { + location: "Thunder Bay".to_string(), + ..node1.clone() + }; + + let node3 = mix::Node { + location: "Warsaw".to_string(), + ..node1.clone() + }; + + let mut mixes: HashMap> = HashMap::new(); + mixes.insert(1, vec![node1, node2]); + mixes.insert(2, vec![node3]); + + let topology = NymTopology::new(mixes, vec![]); + let mixvec = topology.mixes_as_vec(); + assert!(mixvec + .iter() + .map(|node| node.location.clone()) + .collect::>() + .contains(&"London".to_string())); + } + } + + #[cfg(test)] + mod when_no_nodes_exist { + use super::*; + + #[test] + fn returns_an_empty_vec() { + let topology = NymTopology::new(HashMap::new(), vec![]); + let mixvec = topology.mixes_as_vec(); + assert!(mixvec.is_empty()); } } } diff --git a/common/topology/src/mix.rs b/common/topology/src/mix.rs index b25d8e995f..3ee90e4d53 100644 --- a/common/topology/src/mix.rs +++ b/common/topology/src/mix.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::filter; -use crypto::asymmetric::encryption; +use crypto::asymmetric::{encryption, identity}; use nymsphinx_addressing::nodes::NymNodeRoutingAddress; use nymsphinx_types::Node as SphinxNode; use std::convert::TryInto; @@ -23,9 +23,11 @@ use std::net::SocketAddr; pub struct Node { pub location: String, pub host: SocketAddr, - pub pub_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519 + pub identity_key: identity::PublicKey, + pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519 pub layer: u64, - pub last_seen: u64, + pub registration_time: i64, + pub reputation: i64, pub version: String, } @@ -39,6 +41,6 @@ impl<'a> Into for &'a Node { fn into(self) -> SphinxNode { let node_address_bytes = NymNodeRoutingAddress::from(self.host).try_into().unwrap(); - SphinxNode::new(node_address_bytes, (&self.pub_key).into()) + SphinxNode::new(node_address_bytes, (&self.sphinx_key).into()) } } diff --git a/common/version-checker/src/lib.rs b/common/version-checker/src/lib.rs index 3503c89a7b..46aafc2c80 100644 --- a/common/version-checker/src/lib.rs +++ b/common/version-checker/src/lib.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use semver::Version; +use semver::SemVerError; +pub use semver::Version; /// Checks whether given `version` is compatible with a given semantic version requirement `req` /// according to major-minor semver rules. The semantic version requirement can be passed as a full, @@ -32,6 +33,10 @@ pub fn is_minor_version_compatible(version: &str, req: &str) -> bool { expected_version.major == req_version.major && expected_version.minor == req_version.minor } +pub fn parse_version(raw_version: &str) -> Result { + Version::parse(raw_version) +} + #[cfg(test)] mod tests { use super::*; diff --git a/common/wasm-utils/src/websocket/mod.rs b/common/wasm-utils/src/websocket/mod.rs index 7d11080903..2450186fa7 100644 --- a/common/wasm-utils/src/websocket/mod.rs +++ b/common/wasm-utils/src/websocket/mod.rs @@ -214,7 +214,7 @@ impl Drop for JSWebsocket { impl Stream for JSWebsocket { type Item = Result; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // if there's anything in the internal queue, keep returning that let ws_message = self.message_queue.borrow_mut().pop_front(); match ws_message { @@ -238,7 +238,7 @@ impl Stream for JSWebsocket { impl Sink for JSWebsocket { type Error = WsError; - fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.state() { State::Connecting => { // clone the waker to be able to notify the executor once we get connected @@ -280,7 +280,7 @@ impl Sink for JSWebsocket { Poll::Ready(Ok(())) } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.state() { State::Open | State::Connecting => { // TODO: do we need to wait for closing event here? diff --git a/explorer/Cargo.lock b/explorer/Cargo.lock new file mode 100644 index 0000000000..738e0c9127 --- /dev/null +++ b/explorer/Cargo.lock @@ -0,0 +1,2040 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aead" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf01b9b56e767bb57b94ebf91a58b338002963785cdd7013e21c0d4679471e4" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "aes" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" +dependencies = [ + "aes-soft", + "aesni", + "block-cipher-trait", +] + +[[package]] +name = "aes-gcm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834a6bda386024dbb7c8fc51322856c10ffe69559f972261c868485f5759c638" +dependencies = [ + "aead", + "aes", + "block-cipher-trait", + "ghash", + "subtle 2.3.0", + "zeroize", +] + +[[package]] +name = "aes-soft" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" +dependencies = [ + "block-cipher-trait", + "byteorder", + "opaque-debug 0.2.3", +] + +[[package]] +name = "aesni" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" +dependencies = [ + "block-cipher-trait", + "opaque-debug 0.2.3", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-cipher-trait" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "cc" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cookie" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5795cda0897252e34380a27baf884c53aa7ad9990329cdad96d4c5d027015d44" +dependencies = [ + "aes-gcm", + "base64 0.12.3", + "hkdf", + "hmac", + "percent-encoding 2.1.0", + "rand", + "sha2", + "time", +] + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.3", + "subtle 1.0.0", +] + +[[package]] +name = "devise" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74e04ba2d03c5fa0d954c061fc8c9c288badadffc272ebb87679a89846de3ed3" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "066ceb7928ca93a9bedc6d0e612a8a0424048b0ab1f75971b203d01420c055d7" +dependencies = [ + "devise_core", + "quote 0.6.13", +] + +[[package]] +name = "devise_core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf41c59b22b5e3ec0ea55c7847e5f358d340f3a8d6d53a5cf4f1564967f96487" +dependencies = [ + "bitflags", + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "dtoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" + +[[package]] +name = "encoding_rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "filetime" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c122a393ea57648015bf06fbd3d372378992e86b9ff5a7a497b076a28c79efe" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding 2.1.0", +] + +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures-channel" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46" + +[[package]] +name = "futures-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe" +dependencies = [ + "proc-macro-hack", + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11" + +[[package]] +name = "futures-task" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project 1.0.1", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check 0.9.2", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0930ed19a7184089ea46d2fedead2f6dc2b674c5db4276b7da336c7cd83252" +dependencies = [ + "polyval", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + +[[package]] +name = "hkdf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" +dependencies = [ + "digest 0.8.1", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest 0.8.1", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +dependencies = [ + "base64 0.9.3", + "httparse", + "language-tags", + "log 0.3.9", + "mime 0.2.6", + "num_cpus", + "time", + "traitobject", + "typeable", + "unicase 1.4.2", + "url 1.7.2", +] + +[[package]] +name = "hyper" +version = "0.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project 1.0.1", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" +dependencies = [ + "bytes", + "hyper 0.13.9", + "native-tls", + "tokio", + "tokio-tls", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inotify" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" +dependencies = [ + "libc", +] + +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "js-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.11", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime 0.3.16", + "unicase 2.6.0", +] + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log 0.4.11", + "miow 0.2.1", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log 0.4.11", + "mio", + "slab", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" +dependencies = [ + "log 0.4.11", + "mio", + "miow 0.3.5", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +dependencies = [ + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "native-tls" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a1cda389c26d6b88f3d2dc38aa1b750fe87d298cc5d795ec9e975f402f00372" +dependencies = [ + "lazy_static", + "libc", + "log 0.4.11", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net2" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "notify" +version = "4.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" +dependencies = [ + "bitflags", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "libc", + "mio", + "mio-extras", + "walkdir", + "winapi 0.3.9", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "nym-explorer" +version = "0.9.0" +dependencies = [ + "clap", + "futures-util", + "log 0.4.11", + "reqwest", + "rocket", + "rocket_contrib", + "tokio", + "tokio-native-tls", + "tokio-tungstenite", +] + +[[package]] +name = "once_cell" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" +dependencies = [ + "bitflags", + "cfg-if 0.1.10", + "foreign-types", + "lazy_static", + "libc", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pear" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5320f212db967792b67cfe12bd469d08afd6318a249bd917d5c19bc92200ab8a" +dependencies = [ + "pear_codegen", +] + +[[package]] +name = "pear_codegen" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc1c836fdc3d1ef87c348b237b5b5c4dff922156fb2d968f57734f9669768ca" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", + "version_check 0.9.2", + "yansi", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +dependencies = [ + "pin-project-internal 0.4.27", +] + +[[package]] +name = "pin-project" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841" +dependencies = [ + "pin-project-internal 1.0.1", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.48", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.48", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "polyval" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec3341498978de3bfd12d1b22f1af1de22818f5473a11e8a6ef997989e3a212" +dependencies = [ + "cfg-if 0.1.10", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid 0.2.1", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2 1.0.24", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e" +dependencies = [ + "base64 0.12.3", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper 0.13.9", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log 0.4.11", + "mime 0.3.16", + "mime_guess", + "native-tls", + "percent-encoding 2.1.0", + "pin-project-lite", + "serde", + "serde_urlencoded", + "tokio", + "tokio-tls", + "url 2.2.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rocket" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6130967b369cfb8411b0b73e96fcba1229c32a9cc6f295d144f879bfced13c6e" +dependencies = [ + "atty", + "base64 0.12.3", + "log 0.4.11", + "memchr", + "num_cpus", + "pear", + "rocket_codegen", + "rocket_http", + "state", + "time", + "toml", + "version_check 0.9.2", + "yansi", +] + +[[package]] +name = "rocket_codegen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb852e6da168fb948a8f2b798ba2e2f0e4fc860eae0efa9cf2bf0f5466bb0425" +dependencies = [ + "devise", + "glob", + "indexmap", + "quote 0.6.13", + "rocket_http", + "version_check 0.9.2", + "yansi", +] + +[[package]] +name = "rocket_contrib" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3946ca815127041d8f64455561031d058c22ae1b135251502c5ea523cf9e14b" +dependencies = [ + "log 0.4.11", + "notify", + "rocket", + "serde", + "serde_json", +] + +[[package]] +name = "rocket_http" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aff5a5480175f2f553a876b251e9350c74196128806d176da3a51c82aab5428" +dependencies = [ + "cookie", + "hyper 0.10.16", + "indexmap", + "pear", + "percent-encoding 1.0.1", + "smallvec", + "state", + "time", + "unicode-xid 0.1.0", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "security-framework" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" + +[[package]] +name = "serde_json" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url 2.2.0", +] + +[[package]] +name = "sha-1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" + +[[package]] +name = "socket2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "state" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.7", + "unicode-xid 0.2.1", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + +[[package]] +name = "tinyvec" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" + +[[package]] +name = "tokio" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-named-pipes", + "mio-uds", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.48", +] + +[[package]] +name = "tokio-native-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd608593a919a8e05a7d1fc6df885e40f6a88d3a70a3a7eff23ff27964eda069" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e878ad426ca286e4dcae09cbd4e1973a7f8987d97570e2469703dd7f5720c" +dependencies = [ + "futures-util", + "log 0.4.11", + "native-tls", + "pin-project 0.4.27", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log 0.4.11", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" + +[[package]] +name = "tracing" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27" +dependencies = [ + "cfg-if 0.1.10", + "log 0.4.11", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +dependencies = [ + "pin-project 0.4.27", + "tracing", +] + +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" +dependencies = [ + "base64 0.12.3", + "byteorder", + "bytes", + "http", + "httparse", + "input_buffer", + "log 0.4.11", + "native-tls", + "rand", + "sha-1", + "url 2.2.0", + "utf-8", +] + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +dependencies = [ + "version_check 0.1.5", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check 0.9.2", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "universal-hash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01" +dependencies = [ + "generic-array 0.12.3", + "subtle 2.3.0", +] + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + +[[package]] +name = "url" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" +dependencies = [ + "form_urlencoded", + "idna 0.2.0", + "matches", + "percent-encoding 2.1.0", +] + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + +[[package]] +name = "vcpkg" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.11", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" +dependencies = [ + "cfg-if 0.1.10", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" +dependencies = [ + "bumpalo", + "lazy_static", + "log 0.4.11", + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" +dependencies = [ + "cfg-if 0.1.10", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" +dependencies = [ + "quote 1.0.7", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" + +[[package]] +name = "web-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "yansi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71" + +[[package]] +name = "zeroize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a" diff --git a/explorer/Cargo.toml b/explorer/Cargo.toml new file mode 100644 index 0000000000..7622d6a955 --- /dev/null +++ b/explorer/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nym-explorer" +version = "0.9.0" +authors = ["dave ", "Jedrzej Stuczynski "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "2.33" +# no point in importing entire futures crate +futures-util = "0.3" +log = "0.4" +reqwest = "0.10.8" +rocket = "0.4.5" +rocket_contrib = "0.4.5" +tokio = { version = "0.2", features = ["full"] } +tokio-native-tls = "0.1.0" +tokio-tungstenite = {version = "0.11", features = ["tls"] } + +# tungstenite = "0.11" diff --git a/explorer/README.md b/explorer/README.md new file mode 100644 index 0000000000..2d0dad80d6 --- /dev/null +++ b/explorer/README.md @@ -0,0 +1,15 @@ +The Nym Explorer +================ + +Displays nodes and metrics for the Nym network. Will eventually include a UI for viewing mixnodes, mixing rates, entropy levels, and a block explorer. + + +Development +----------- + +The code uses [Rocket](https://rocket.rs), which requires Rust nightly for the moment. + +You can override `rustup` on a per-directory basis from the `explorer` directory by doing `rustup override set nightly`. + +Then just `cargo run` like normal, no `+nightly` stuff needed. + diff --git a/explorer/public/assets/css/custom.css b/explorer/public/assets/css/custom.css new file mode 100644 index 0000000000..cf82967ca2 --- /dev/null +++ b/explorer/public/assets/css/custom.css @@ -0,0 +1,62 @@ +th { + cursor: pointer; +} + +:root { + --status-indicator-size: 10px; + --status-indicator-animation-duration: 2s; + + --status-indicator-color: rgb(216, 226, 233); + --status-indicator-color-semi: rgba(216, 226, 233, .5); + --status-indicator-color-transparent: rgba(216, 226, 233, 0); + + --status-indicator-color-active: rgb(0, 149, 255); + --status-indicator-color-active-semi: rgba(0, 149, 255, .5); + --status-indicator-color-active-transparent: rgba(0, 149, 255, 0); + + --status-indicator-color-positive: rgb(75, 210, 143); + --status-indicator-color-positive-semi: rgba(75, 210, 143, .5); + --status-indicator-color-positive-transparent: rgba(75, 210, 143, 0); + + --status-indicator-color-intermediary: rgb(255, 170, 0); + --status-indicator-color-intermediary-semi: rgba(255, 170, 0, .5); + --status-indicator-color-intermediary-transparent: rgba(255, 170, 0, 0); + + --status-indicator-color-negative: rgb(255, 77, 77); + --status-indicator-color-negative-semi: rgba(255, 77, 77, .5); + --status-indicator-color-negative-transparent: rgba(255, 77, 77, 0); + } + + status-indicator { + display: inline-block; + border-radius: 50%; + cursor: pointer; + width: var(--status-indicator-size); + height: var(--status-indicator-size); + background-color: var(--status-indicator-color); + } + + status-indicator[active] { + background-color: var(--status-indicator-color-active); + } + + status-indicator[positive] { + background-color: var(--status-indicator-color-positive); + animation-name: status-indicator-pulse-positive; + } + + status-indicator[intermediary] { + background-color: var(--status-indicator-color-intermediary); + } + + status-indicator[negative] { + background-color: var(--status-indicator-color-negative); + animation-name: status-indicator-pulse-negative; + } + + .metadata { + color: #51cbce; + font-weight: bold; + margin-bottom: 10px; + } + diff --git a/explorer/public/assets/js/main.js b/explorer/public/assets/js/main.js new file mode 100644 index 0000000000..e0dde17a05 --- /dev/null +++ b/explorer/public/assets/js/main.js @@ -0,0 +1,288 @@ +function websocketUrl() { + if ($(location).attr("href").startsWith("http://localhost")) { + return "ws://localhost:1648"; + } else if ($(location).attr("href").startsWith("http://qa-explorer")) { + return "ws://qa-explorer.nymtech.net:1648"; + } else { + return "wss://testnet-explorer.nymtech.net"; + } +} + +function getTopology() { + console.log("Getting topology..."); + var topologyUrl = "/downloads/topology.json"; + $.ajax({ + type: 'GET', + url: topologyUrl, + success: function (data) { + createMixnodeCount(data.mixNodes.length); + createValidatorCount(data.validators.validators.length); + createBlockHeight(data.validators.block_height); + createDisplayTable(data); + updateNodesStatus(); + } + }); +} + +function createDisplayTable(data) { + createMixnodeRows(data.mixNodes); + createValidatorRows(data.validators.validators); + createGatewayRows(data.gateways); +} + +function clearStatus(element) { + element.removeAttribute("active") + element.removeAttribute("positive") + element.removeAttribute("intermediary") + element.removeAttribute("negative") +} + +function setNodeStatus(dotWrapper, reportData) { + let statusIndicator = dotWrapper.children[0]; + clearStatus(statusIndicator) + + if (reportData == undefined || reportData == null) { + dotWrapper.setAttribute("title", "no data available") + return + } + + + if (reportData.mostRecentIPV4 && reportData.mostRecentIPV6 && reportData.lastHourIPV4 > 50 && reportData.lastHourIPV6 > 50) { + statusIndicator.setAttribute("positive", "") + } else if (reportData.mostRecentIPV4 || reportData.mostRecentIPV6) { + statusIndicator.setAttribute("intermediary", "") + } else { + statusIndicator.setAttribute("negative", "") + } + + let newTooltip = `\n + IPv4 routable: ${reportData.mostRecentIPV4}\n + Last hour IPv4: ${reportData.lastHourIPV4}%\n + IPv6 routable: ${reportData.mostRecentIPV6}\n + Last hour IPv6: ${reportData.lastHourIPV6}%\n + ` + dotWrapper.setAttribute("title", newTooltip) +} + +function dealWithStatusReport(report) { + let reportMap = new Map(); + report.forEach(reportData => { + reportMap.set(reportData.pubKey, reportData) + }) + + let allWrappers = document.getElementsByClassName('statusDot'); + for (let statusWrapper of allWrappers) { + let mapEntry = reportMap.get(statusWrapper.getAttribute('pubkey')) + setNodeStatus(statusWrapper, mapEntry) + } +} + +function updateNodesStatus() { + console.log("updating node statuses!") + + const reportUrl = "/downloads/mixmining.json"; + fetch(reportUrl, { + method: 'GET' + }) + .then((response) => response.json()) + .then((data) => dealWithStatusReport(data.report)).catch((err) => { + console.log("getting full mixmining report failed - ", err) + }) +} + +function makeStatusDot(nodePubKey) { + let statusText = "pending..." + + let dotWrapper = document.createElement("div"); + dotWrapper.setAttribute('id', `dotWrapper${nodePubKey}`) + dotWrapper.setAttribute('pubkey', nodePubKey) + dotWrapper.setAttribute('style', 'text-align: center') + dotWrapper.setAttribute('data-toggle', 'tooltip') + dotWrapper.setAttribute('data-placement', 'right') + dotWrapper.setAttribute('title', statusText) + dotWrapper.classList.add('statusDot') + + let dot = document.createElement("status-indicator"); + dotWrapper.appendChild(dot); + + return dotWrapper; +} + +function setGatewayStatusDot(nodePubKey) { + let statusText = "Data not available..." + let dotWrapper = document.getElementById(`dotWrapper${nodePubKey}`); + dotWrapper.classList.remove('statusDot') + let statusIndicator = dotWrapper.children[0]; + clearStatus(statusIndicator); + statusIndicator.setAttribute("active", "") + + dotWrapper.setAttribute("title", statusText) +} + +function createMixnodeCount(mixNodeCount) { + // no need to sanitize numbers (count is obtained via .lengnth attribute of an array) + $('#mixnodes-count').text(mixNodeCount); +} + +function createValidatorCount(validatorCount) { + // no need to sanitize numbers (count is obtained via .lengnth attribute of an array) + $('#validators-count').text(validatorCount); +} + +function createBlockHeight(blockHeight) { + let purifiedHeight = DOMPurify.sanitize(blockHeight) + if (purifiedHeight.length === 0) { + purifiedHeight = 0 + } + + $('#block-height').text(purifiedHeight); +} + +function compareNodes(node1, node2) { + if (node1.reputation < node2.reputation) { + return 1 + } else if (node1.reputation > node2.reputation) { + return -1 + } else { + if (node1.version < node2.version) { + return 1 + } else if (node1.version > node2.version) { + return -1 + } else { + if (node1.layer < node2.layer) { + return 1 + } else { + return -1 + } + } + } +} + +function createMixnodeRows(mixNodes) { + mixNodes.sort(compareNodes) + + const currentUnixTime = new Date().getTime() * 1000000; + + mixNodes.forEach(node => { + // because javascript works in mysterious ways, if you sanitize "0", it will return "" + let purifiedRep = DOMPurify.sanitize(node.reputation) + if (purifiedRep.length === 0) { + purifiedRep = 0 + } + var $tr = $('').append( + $(' '), + $('').html(makeStatusDot(node.identityKey)), + $('').text(purifiedRep), + $('').text(DOMPurify.sanitize(node.version)), + $('').text(DOMPurify.sanitize(node.identityKey)), + $('').text(DOMPurify.sanitize(node.sphinxKey)), + $('').text(DOMPurify.sanitize(node.location)), + $('').text(DOMPurify.sanitize(node.mixHost)), + $('').text(DOMPurify.sanitize(node.layer)), + $('').text("0"), + $('').text("0") + ).appendTo('#mixnodes-list'); + }) +} + +function createGatewayRows(gatewayNodes) { + gatewayNodes.forEach(node => { + // because javascript works in mysterious ways, if you sanitize "0", it will return "" + let purifiedRep = DOMPurify.sanitize(node.reputation) + if (purifiedRep.length === 0) { + purifiedRep = 0 + } + var $tr = $('').append( + $(' '), + $('').html(makeStatusDot(node.identityKey)), + $('').text(purifiedRep), + $('').text(DOMPurify.sanitize(node.version)), + $('').text(DOMPurify.sanitize(node.identityKey)), + $('').text(DOMPurify.sanitize(node.sphinxKey)), + $('').text(DOMPurify.sanitize(node.location)), + $('').text(DOMPurify.sanitize(node.mixHost)), + $('').text(DOMPurify.sanitize(node.clientsHost)), + ).appendTo('#gatewaynodes-list'); + + setGatewayStatusDot(node.identityKey); + }) +} + +function createValidatorRows(validators) { + validators.forEach(validator => { + var $tr = $('').append( + $('').text(DOMPurify.sanitize(validator.address)), + $('').text(DOMPurify.sanitize(validator.pub_key)), + $('').text(DOMPurify.sanitize(validator.proposer_priority)), + $('').text(DOMPurify.sanitize(validator.voting_power)) + ).appendTo('#validator-list'); + }) +} + +function connectWebSocket() { + var conn; + var url; + url = websocketUrl() + "/ws"; + console.log("connecting to: " + url); + conn = new WebSocket(url); + conn.onmessage = function (evt) { + processMessage(evt); + }; +} + +function processMessage(evt) { + var messages = evt.data.split('\n'); + for (var i = 0; i < messages.length; i++) { + var msg = jQuery.parseJSON(messages[i]); + prevTimestamp = updateTimeStampStorage(msg); + + timeDiff = (msg.timestamp - prevTimeStamp) / 1000000000; + displayReceivedPackets(msg, timeDiff); + displaySentPackets(msg, timeDiff); + } +} + +function displaySentPackets(msg, timeDiff) { + var sentCell = "#sent-" + DOMPurify.sanitize(msg.pubKey); + var sent = 0; + for (var key in msg.sent) { + s = msg.sent[key]; + sent += s; + } + sentPerSecond = Math.floor(sent / timeDiff); + let sentVal = DOMPurify.sanitize(sentPerSecond).length > 0 ? DOMPurify.sanitize(sentPerSecond) : "0"; + $(sentCell).html(sentVal); +} + +function displayReceivedPackets(msg, timeDiff) { + receivedPerSecond = Math.floor(msg.received / timeDiff); + var recCell = "#received-" + DOMPurify.sanitize(msg.pubKey); + let recVal = DOMPurify.sanitize(receivedPerSecond).length > 0 ? DOMPurify.sanitize(receivedPerSecond) : "0"; + $(recCell).html(recVal); +} + +/* + Hahahaha this has to be the crappiest code I've written since learning to code. + + On the upside, it'll save a few weeks messing with React or Angular to do + basically the same thing. +*/ +function updateTimeStampStorage(msg) { + // get the timestamp stored during the last loop + prevTimeStamp = ($("#prev-timestamp-" + msg.pubKey).val()) + + // store the current timestamp + $('#prev-timestamp-' + msg.pubKey).val(msg.timestamp); + + // return the previous timestamp + return prevTimeStamp; +} + + +document.addEventListener("DOMContentLoaded", function () { + // update every minute + setInterval(updateNodesStatus, 60000); + getTopology(); + connectWebSocket(); +}); + diff --git a/explorer/public/assets/js/purify.js b/explorer/public/assets/js/purify.js new file mode 100644 index 0000000000..cdce3a0bcf --- /dev/null +++ b/explorer/public/assets/js/purify.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.DOMPurify=t()}(this,function(){"use strict";function e(e,t){y&&y(e,null);for(var n=t.length;n--;){var r=t[n];if("string"==typeof r){var o=r.toLowerCase();o!==r&&(Object.isFrozen(t)||(t[n]=o),r=o)}e[r]=!0}return e}function t(e){var t={},n=void 0;for(n in e)g(h,e,[n])&&(t[n]=e[n]);return t}function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:O(),u=function(e){return r(e)};if(u.version="2.0.3",u.removed=[],!o||!o.document||9!==o.document.nodeType)return u.isSupported=!1,u;var h=o.document,y=!1,g=!1,v=!1,R=o.document,D=o.DocumentFragment,C=o.HTMLTemplateElement,H=o.Node,F=o.NodeFilter,z=o.NamedNodeMap,I=void 0===z?o.NamedNodeMap||o.MozNamedAttrMap:z,U=o.Text,j=o.Comment,P=o.DOMParser,W=o.TrustedTypes;if("function"==typeof C){var B=R.createElement("template");B.content&&B.content.ownerDocument&&(R=B.content.ownerDocument)}var q=N(W,h),G=q?q.createHTML(""):"",V=R,Y=V.implementation,K=V.createNodeIterator,X=V.getElementsByTagName,$=V.createDocumentFragment,J=h.importNode,Q={};u.isSupported=Y&&void 0!==Y.createHTMLDocument&&9!==R.documentMode;var Z=T,ee=b,te=A,ne=x,re=S,oe=M,ie=L,ae=null,le=e({},[].concat(n(i),n(a),n(l),n(c),n(s))),ce=null,se=e({},[].concat(n(d),n(f),n(p),n(m))),ue=null,de=null,fe=!0,pe=!0,me=!1,he=!1,ye=!1,ge=!1,ve=!1,Te=!1,be=!1,Ae=!1,xe=!1,Le=!1,Se=!0,Me=!0,Ee=!1,we={},ke=e({},["audio","colgroup","head","math","script","style","template","thead","svg","video"]),_e=e({},["audio","video","img","source","image"]),Oe=null,Ne=e({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),Re=null,De=R.createElement("form"),Ce=function(r){Re&&Re===r||(r&&"object"===(void 0===r?"undefined":E(r))||(r={}),ae="ALLOWED_TAGS"in r?e({},r.ALLOWED_TAGS):le,ce="ALLOWED_ATTR"in r?e({},r.ALLOWED_ATTR):se,Oe="ADD_URI_SAFE_ATTR"in r?e(t(Ne),r.ADD_URI_SAFE_ATTR):Ne,ue="FORBID_TAGS"in r?e({},r.FORBID_TAGS):{},de="FORBID_ATTR"in r?e({},r.FORBID_ATTR):{},we="USE_PROFILES"in r&&r.USE_PROFILES,fe=!1!==r.ALLOW_ARIA_ATTR,pe=!1!==r.ALLOW_DATA_ATTR,me=r.ALLOW_UNKNOWN_PROTOCOLS||!1,he=r.SAFE_FOR_JQUERY||!1,ye=r.SAFE_FOR_TEMPLATES||!1,ge=r.WHOLE_DOCUMENT||!1,be=r.RETURN_DOM||!1,Ae=r.RETURN_DOM_FRAGMENT||!1,xe=r.RETURN_DOM_IMPORT||!1,Le=r.RETURN_TRUSTED_TYPE||!1,Te=r.FORCE_BODY||!1,Se=!1!==r.SANITIZE_DOM,Me=!1!==r.KEEP_CONTENT,Ee=r.IN_PLACE||!1,ie=r.ALLOWED_URI_REGEXP||ie,ye&&(pe=!1),Ae&&(be=!0),we&&(ae=e({},[].concat(n(s))),ce=[],!0===we.html&&(e(ae,i),e(ce,d)),!0===we.svg&&(e(ae,a),e(ce,f),e(ce,m)),!0===we.svgFilters&&(e(ae,l),e(ce,f),e(ce,m)),!0===we.mathMl&&(e(ae,c),e(ce,p),e(ce,m))),r.ADD_TAGS&&(ae===le&&(ae=t(ae)),e(ae,r.ADD_TAGS)),r.ADD_ATTR&&(ce===se&&(ce=t(ce)),e(ce,r.ADD_ATTR)),r.ADD_URI_SAFE_ATTR&&e(Oe,r.ADD_URI_SAFE_ATTR),Me&&(ae["#text"]=!0),ge&&e(ae,["html","head","body"]),ae.table&&(e(ae,["tbody"]),delete ue.tbody),_&&_(r),Re=r)},He=function(e){u.removed.push({element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=G}},Fe=function(e,t){try{u.removed.push({attribute:t.getAttributeNode(e),from:t})}catch(e){u.removed.push({attribute:null,from:t})}t.removeAttribute(e)},ze=function(t){var n=void 0,r=void 0;if(Te)t=""+t;else{var o=t.match(/^[\s]+/);(r=o&&o[0])&&(t=t.slice(r.length))}if(y)try{n=(new P).parseFromString(t,"text/html")}catch(e){}if(v&&e(ue,["title"]),!n||!n.documentElement){var i=(n=Y.createHTMLDocument("")).body;i.parentNode.removeChild(i.parentNode.firstElementChild),i.outerHTML=q?q.createHTML(t):t}return r&&n.body.insertBefore(R.createTextNode(r),n.body.childNodes[0]||null),X.call(n,ge?"html":"body")[0]};u.isSupported&&(function(){try{ze('

').querySelector("svg img")&&(y=!0)}catch(e){}}(),function(){try{ze("</title><img>").querySelector("title").innerHTML.match(/<\/title/)&&(v=!0)}catch(e){}}(),function(){try{ze("<svg></p></svg>").querySelector("svg p")&&(g=!0)}catch(e){}}());var Ie=function(e){return K.call(e.ownerDocument||e,e,F.SHOW_ELEMENT|F.SHOW_COMMENT|F.SHOW_TEXT,function(){return F.FILTER_ACCEPT},!1)},Ue=function(e){return!(e instanceof U||e instanceof j)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof I&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute)},je=function(e){return"object"===(void 0===H?"undefined":E(H))?e instanceof H:e&&"object"===(void 0===e?"undefined":E(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},Pe=function(e,t,n){Q[e]&&Q[e].forEach(function(e){e.call(u,t,n,Re)})},We=function(e){var t=void 0;if(Pe("beforeSanitizeElements",e,null),Ue(e))return He(e),!0;var n=e.nodeName.toLowerCase();if(Pe("uponSanitizeElement",e,{tagName:n,allowedTags:ae}),!ae[n]||ue[n]){if(Me&&!ke[n]&&"function"==typeof e.insertAdjacentHTML)try{var r=e.innerHTML;e.insertAdjacentHTML("AfterEnd",q?q.createHTML(r):r)}catch(e){}return He(e),!0}return"noscript"===n&&e.innerHTML.match(/<\/noscript/i)?(He(e),!0):"noembed"===n&&e.innerHTML.match(/<\/noembed/i)?(He(e),!0):e.namespaceURI&&e.namespaceURI.match(/svg|math/i)&&e.textContent&&e.textContent.match(new RegExp("</"+n,"i"))?(He(e),!0):"svg"!==n&&"math"!==n||!(e.innerHTML&&e.innerHTML.match(/<template/i)||void 0===e.innerHTML&&g)?(!he||e.firstElementChild||e.content&&e.content.firstElementChild||!/</g.test(e.textContent)||(u.removed.push({element:e.cloneNode()}),e.innerHTML?e.innerHTML=e.innerHTML.replace(/</g,"<"):e.innerHTML=e.textContent.replace(/</g,"<")),ye&&3===e.nodeType&&(t=(t=(t=e.textContent).replace(Z," ")).replace(ee," "),e.textContent!==t&&(u.removed.push({element:e.cloneNode()}),e.textContent=t)),Pe("afterSanitizeElements",e,null),!1):(He(e),!0)},Be=function(e,t,n){if(Se&&("id"===t||"name"===t)&&(n in R||n in De))return!1;if(pe&&te.test(t));else if(fe&&ne.test(t));else{if(!ce[t]||de[t])return!1;if(Oe[t]);else if(ie.test(n.replace(oe,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==n.indexOf("data:")||!_e[e]){if(me&&!re.test(n.replace(oe,"")));else if(n)return!1}else;}return!0},qe=function(e){var t=void 0,n=void 0,r=void 0,o=void 0,i=void 0;Pe("beforeSanitizeAttributes",e,null);var a=e.attributes;if(a){var l={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:ce};for(i=a.length;i--;){var c=t=a[i],s=c.name,d=c.namespaceURI;if(n=t.value.trim(),r=s.toLowerCase(),l.attrName=r,l.attrValue=n,l.keepAttr=!0,Pe("uponSanitizeAttribute",e,l),n=l.attrValue,g&&n.match(/<\//)&&He(e),"name"===r&&"IMG"===e.nodeName&&a.id)o=a.id,a=w(k,a,[]),Fe("id",e),Fe(s,e),a.indexOf(o)>i&&e.setAttribute("id",o.value);else{if("INPUT"===e.nodeName&&"type"===r&&"file"===n&&l.keepAttr&&(ce[r]||!de[r]))continue;"id"===s&&e.setAttribute(s,""),Fe(s,e)}if(l.keepAttr){ye&&(n=(n=n.replace(Z," ")).replace(ee," "));var f=e.nodeName.toLowerCase();if(Be(f,r,n))try{d?e.setAttributeNS(d,s,n):e.setAttribute(s,n),u.removed.pop()}catch(e){}}}Pe("afterSanitizeAttributes",e,null)}},Ge=function e(t){var n=void 0,r=Ie(t);for(Pe("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)Pe("uponSanitizeShadowNode",n,null),We(n)||(n.content instanceof D&&e(n.content),qe(n));Pe("afterSanitizeShadowDOM",t,null)};return u.sanitize=function(e,t){var n=void 0,r=void 0,i=void 0,a=void 0,l=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!je(e)){if("function"!=typeof e.toString)throw new TypeError("toString is not a function");if("string"!=typeof(e=e.toString()))throw new TypeError("dirty is not a string, aborting")}if(!u.isSupported){if("object"===E(o.toStaticHTML)||"function"==typeof o.toStaticHTML){if("string"==typeof e)return o.toStaticHTML(e);if(je(e))return o.toStaticHTML(e.outerHTML)}return e}if(ve||Ce(t),u.removed=[],Ee);else if(e instanceof H)1===(r=(n=ze("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===r.nodeName?n=r:"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!be&&!ye&&!ge&&Le&&-1===e.indexOf("<"))return q?q.createHTML(e):e;if(!(n=ze(e)))return be?null:G}n&&Te&&He(n.firstChild);for(var c=Ie(Ee?e:n);i=c.nextNode();)3===i.nodeType&&i===a||We(i)||(i.content instanceof D&&Ge(i.content),qe(i),a=i);if(a=null,Ee)return e;if(be){if(Ae)for(l=$.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return xe&&(l=J.call(h,l,!0)),l}var s=ge?n.outerHTML:n.innerHTML;return ye&&(s=(s=s.replace(Z," ")).replace(ee," ")),q&&Le?q.createHTML(s):s},u.setConfig=function(e){Ce(e),ve=!0},u.clearConfig=function(){Re=null,ve=!1},u.isValidAttribute=function(e,t,n){Re||Ce({});var r=e.toLowerCase(),o=t.toLowerCase();return Be(r,o,n)},u.addHook=function(e,t){"function"==typeof t&&(Q[e]=Q[e]||[],Q[e].push(t))},u.removeHook=function(e){Q[e]&&Q[e].pop()},u.removeHooks=function(e){Q[e]&&(Q[e]=[])},u.removeAllHooks=function(){Q={}},u}var o=Object.freeze||function(e){return e},i=o(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),a=o(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","audio","canvas","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","video","view","vkern"]),l=o(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),c=o(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"]),s=o(["#text"]),u=Object.freeze||function(e){return e},d=u(["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","coords","crossorigin","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","integrity","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","minlength","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns"]),f=u(["accent-height","accumulate","additive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),p=u(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),m=u(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),h=Object.hasOwnProperty,y=Object.setPrototypeOf,g=("undefined"!=typeof Reflect&&Reflect).apply;g||(g=function(e,t,n){return e.apply(t,n)});var v=Object.seal||function(e){return e},T=v(/\{\{[\s\S]*|[\s\S]*\}\}/gm),b=v(/<%[\s\S]*|[\s\S]*%>/gm),A=v(/^data-[\-\w.\u00B7-\uFFFF]/),x=v(/^aria-[\-\w]+$/),L=v(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),S=v(/^(?:\w+script|data):/i),M=v(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g),E="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},w=("undefined"!=typeof Reflect&&Reflect).apply,k=Array.prototype.slice,_=Object.freeze,O=function(){return"undefined"==typeof window?null:window};w||(w=function(e,t,n){return e.apply(t,n)});var N=function(e,t){if("object"!==(void 0===e?"undefined":E(e))||"function"!=typeof e.createPolicy)return null;var n=null;t.currentScript&&t.currentScript.hasAttribute("data-tt-policy-suffix")&&(n=t.currentScript.getAttribute("data-tt-policy-suffix"));var r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:function(e){return e}})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}};return r()}); +//# sourceMappingURL=purify.min.js.map diff --git a/explorer/public/assets/js/tablesorter.js b/explorer/public/assets/js/tablesorter.js new file mode 100644 index 0000000000..80b5b4f41c --- /dev/null +++ b/explorer/public/assets/js/tablesorter.js @@ -0,0 +1,14 @@ +$('th').click(function(){ + var table = $(this).parents('table').eq(0) + var rows = table.find('tr:gt(0)').toArray().sort(comparer($(this).index())) + this.asc = !this.asc + if (!this.asc){rows = rows.reverse()} + for (var i = 0; i < rows.length; i++){table.append(rows[i])} +}) +function comparer(index) { + return function(a, b) { + var valA = getCellValue(a, index), valB = getCellValue(b, index) + return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.toString().localeCompare(valB) + } +} +function getCellValue(row, index){ return $(row).children('td').eq(index).text() } diff --git a/validator/migrations/.gitkeep b/explorer/public/downloads/.gitkeep similarity index 100% rename from validator/migrations/.gitkeep rename to explorer/public/downloads/.gitkeep diff --git a/explorer/public/index.html b/explorer/public/index.html new file mode 100644 index 0000000000..bf4e410bdf --- /dev/null +++ b/explorer/public/index.html @@ -0,0 +1,342 @@ +<!-- +========================================================= + Paper Dashboard 2 - v2.0.0 +========================================================= + + Product Page: https://www.creative-tim.com/product/paper-dashboard-2 + Copyright 2019 Creative Tim (https://www.creative-tim.com) + Licensed under MIT (https://github.com/creativetimofficial/paper-dashboard/blob/master/LICENSE) + + Coded by Creative Tim + +========================================================= + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> + +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8" /> + <link rel="apple-touch-icon" sizes="76x76" href="paper-dashboard/assets/img/apple-icon.png"> + <link rel="icon" type="image/png" href="paper-dashboard/assets/img/favicon.png"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> + <title> + Nym Network Status + + + + + + + + + + + + +

+ +
+ +
+
+ + + +
+
+
+
+
+

Mix Node Count

+
+
+ +
+
+
+
+
+
+

Validators Count

+
+
+ +
+
+
+
+
+
+

Current block height

+
+
+ +
+
+
+ +
+
+
+

Mix Nodes

+
+
+
+ + + + + + + + + + + + + + + + + +
+ Status + + Reputation + + Version + + Identity key + + Sphinx key + + Location + + Host + + Layer + + Received + + Sent +
+
+
+
+
+ +
+
+
+

Gateways

+
+
+
+ + + + + + + + + + + + + + + +
+ Status + + Reputation + + Version + + Identity key + + Sphinx key + + Location + + Mix Host + + Clients Host +
+
+
+
+
+ +
+
+
+

Validators

+
+
+
+ + + + + + + + + + + +
+ Address + + Public Key + + Proposer Priority + + Voting Power +
+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Nym + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/explorer/public/paper-dashboard/CHANGELOG.md b/explorer/public/paper-dashboard/CHANGELOG.md new file mode 100644 index 0000000000..6a1d69cc7d --- /dev/null +++ b/explorer/public/paper-dashboard/CHANGELOG.md @@ -0,0 +1,21 @@ +# Change Log + +## [2.0.0] 22.05.2018 +### Bootstrap 4.1.1 integration & Bug Fixing +- Small changes for components +- ChartJS plugin integration +- Gulp task integration to open the project in browser +- Added License for Nucleo Icons +- Small changes on design of Cards + +## [1.0.2] 08.03.2017 + - added documentation files + +## [1.0.1] 30.09.2016 +### Bugfixing, Improvements +- New Page [current version] +- added Upgrade to PRO page for those who want to upsell inside the dashboard +- switched to MIT license + +## [1.0.0] 29.03.2016 +### Original Release diff --git a/explorer/public/paper-dashboard/LICENSE b/explorer/public/paper-dashboard/LICENSE new file mode 100644 index 0000000000..038a2d415c --- /dev/null +++ b/explorer/public/paper-dashboard/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Creative Tim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/explorer/public/paper-dashboard/README.md b/explorer/public/paper-dashboard/README.md new file mode 100644 index 0000000000..f21914b826 --- /dev/null +++ b/explorer/public/paper-dashboard/README.md @@ -0,0 +1,105 @@ +# [Paper Dashboard 2](https://demos.creative-tim.com/paper-dashboard/examples/dashboard.html) [![version][version-badge]][CHANGELOG] [![license][license-badge]][LICENSE] + +![alt text](https://s3.amazonaws.com/creativetim_bucket/products/86/original/opt_pd2_thumbnail.jpg) + +If you are a developer or a website owner that needs to work within a dashboard and want to have a pretty sight while doing so, **[Paper Dashboard 2](https://demos.creative-tim.com/paper-dashboard/examples/dashboard.html)** is for you. Paper Dashboard is a Bootstrap Admin Panel which combines soft colours with beautiful typography and spacious cards and graphics. It is a powerful tool, but it is light and easy to use. It has enough features to allow you to get the job done, but it is not crowded to the point where you can't find the files for a specific plugin. + +We like consistency and design that blends into its purpose. Paper Dashboard is a perfect example of our most thoughtful work. It combines over a dozen components and plugins, while looking like everything fits together. For an easy start or inspiration for you project, we have also create a set of example pages, like the user settings or usage graphics. + +Paper Dashboard is built using the same design language as **[Paper Kit(https://www.creative-tim.com/product/paper-kit-2)**. You can easily use them together, or pick between them depending on the project you have. + +**Bootstrap 4 Support** +Paper Dashboard 2 is built on top of the much awaited Bootstrap 4. This makes starting a new project very simple. It also provides benefits if you are already working on a Bootstrap 4 project; you can just import the Paper Dashboard style over it. Most of the elements have been redesigned; but if you are using an element we have not touched, it will fall back to the Bootstrap default. + +**Example Pages** +We wanted to fully display the power of this dashboard, so the kit comes packed with examples showing you how to use the components. Inside the product you will find: + +## Links: + ++ [Live Preview](https://demos.creative-tim.com/paper-dashboard/examples/dashboard.html) ++ [Paper Dashboard 2 PRO](https://www.creative-tim.com/product/paper-dashboard-2-pro) (from $39) + +**Tutorial** +In order for you to easily be able to use the Paper Dashboard 2, we have created a tutorial page in our documentation. It shows the structure for the files inside the archive and how to import them. It then features every components with a description and example how to use it. You can see the details [here](https://creativetimofficial.github.io/paper-dashboard-2/docs/1.0/getting-started/introduction.html). + + +### What's included + +Within the download you'll find the following directories and files: + +``` +Paper Dashboard 2 +. +├── CHANGELOG.md +├── README.md +├── assets +│   ├── css/ +│   ├── demo/ +│   ├── fonts/ +│   ├── img/ +│   ├── js +│   │   ├── core/ +│   │   ├── paper-dashboard.js +│   │   ├── paper-dashboard.js.map +│   │   ├── paper-dashboard.min.js +│   │   └── plugins +│   │   ├── bootstrap-notify.js +│   │   ├── chartjs.min.js +│   │   └── perfect-scrollbar.jquery.min.js +│   └── scss/ +│   ├── paper-dashboard/ +│   │   ├── cards/ +│   │   ├── mixins/ +│   │   └── plugins/ +│   └── paper-dashboard.scss +├── docs/ +│   └── documentation.html +├── examples/ +│   ├── dashboard.html +│   ├── icons.html +│   ├── map.html +│   ├── notifications.html +│   ├── tables.html +│   ├── typography.html +│   ├── upgrade.html +│   └── user.html +├── gulpfile.js +├── nucleo-icons.html +└── package.json +``` + +## Getting started +- Download the project’s zip +- Make sure you have node.js (https://nodejs.org/en/) installed +- Type `npm install` in terminal/console in the source folder where `package.json` is located +- You will find all the branding colors inside `assets/scss/core/variables/_brand.scss`. You can change them with a HEX value or with other predefined variables from `assets/scss/core/variables/_colors.scss` +- Run in terminal `gulp compile-scss` for a single compilation or gulp watch for continous compilation of the changes that you make in `*.scss` files. This command should be run in the same folder where `gulpfile.js` and `package.json` are located +- Run in terminal `gulp open-app` for opening the Presentation Page (default) of the product. You can set in `gulpfile.js` from your downloaded archive any page you want to open in browser, `at line 30: gulp.src('./examples/dashboard.html')` + + +## Useful Links + +More products from Creative Tim: + +Tutorials: + +Freebies: + +Affiliate Program (earn money): + +Social Media: + +Twitter: + +Facebook: + +Dribbble: + +Google+: + +Instagram: + +[CHANGELOG]: ./CHANGELOG.md +[LICENSE]: ./LICENSE +[version-badge]: https://img.shields.io/badge/version-1.0.1-blue.svg +[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg diff --git a/explorer/public/paper-dashboard/assets/css/bootstrap.min.css b/explorer/public/paper-dashboard/assets/css/bootstrap.min.css new file mode 100644 index 0000000000..7649fed412 --- /dev/null +++ b/explorer/public/paper-dashboard/assets/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.1.1 (https://getbootstrap.com/) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;max-width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:not([size]):not([multiple]){height:calc(2.25rem + 2px)}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm,.input-group-lg>.form-control-plaintext.form-control,.input-group-lg>.input-group-append>.form-control-plaintext.btn,.input-group-lg>.input-group-append>.form-control-plaintext.input-group-text,.input-group-lg>.input-group-prepend>.form-control-plaintext.btn,.input-group-lg>.input-group-prepend>.form-control-plaintext.input-group-text,.input-group-sm>.form-control-plaintext.form-control,.input-group-sm>.input-group-append>.form-control-plaintext.btn,.input-group-sm>.input-group-append>.form-control-plaintext.input-group-text,.input-group-sm>.input-group-prepend>.form-control-plaintext.btn,.input-group-sm>.input-group-prepend>.form-control-plaintext.input-group-text{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-sm>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-sm>select.form-control:not([size]):not([multiple]),select.form-control-sm:not([size]):not([multiple]){height:calc(1.8125rem + 2px)}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-lg>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-lg>select.form-control:not([size]):not([multiple]),select.form-control-lg:not([size]):not([multiple]){height:calc(2.875rem + 2px)}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(40,167,69,.8);border-radius:.2rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::before,.was-validated .custom-file-input:valid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(220,53,69,.8);border-radius:.2rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::before,.was-validated .custom-file-input:invalid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}.btn:not(:disabled):not(.disabled).active,.btn:not(:disabled):not(.disabled):active{background-image:none}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media screen and (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media screen and (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-right{right:0;left:auto}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file:focus,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-label::after{border-color:#80bdff}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:2.25rem;padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;padding-left:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-appearance:none;appearance:none}.custom-range::-webkit-slider-thumb:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-appearance:none;appearance:none}.custom-range::-moz-range-thumb:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;appearance:none}.custom-range::-ms-thumb:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion .card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion .card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion .card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion .card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media screen and (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:focus,.close:hover{color:#000;text-decoration:none;opacity:.75}.close:not(:disabled):not(.disabled){cursor:pointer}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}@media screen and (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-ms-flex-align:center;align-items:center;width:100%;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}@media screen and (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-fade .carousel-item{opacity:0;transition-duration:.6s;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{opacity:0}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-prev,.carousel-fade .carousel-item-next,.carousel-fade .carousel-item-prev,.carousel-fade .carousel-item.active{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-prev,.carousel-fade .carousel-item-next,.carousel-fade .carousel-item-prev,.carousel-fade .carousel-item.active{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=bootstrap.min.css.map */ diff --git a/explorer/public/paper-dashboard/assets/css/bootstrap.min.css.map b/explorer/public/paper-dashboard/assets/css/bootstrap.min.css.map new file mode 100755 index 0000000000..d291039eb7 --- /dev/null +++ b/explorer/public/paper-dashboard/assets/css/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","bootstrap.css","../../scss/mixins/_hover.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/mixins/_border-radius.scss","../../scss/_code.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/mixins/_breakpoints.scss","../../scss/mixins/_grid-framework.scss","../../scss/_tables.scss","../../scss/mixins/_table-row.scss","../../scss/_forms.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_forms.scss","../../scss/mixins/_gradients.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/mixins/_nav-divider.scss","../../scss/_button-group.scss","../../scss/_input-group.scss","../../scss/_custom-forms.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/mixins/_badge.scss","../../scss/_jumbotron.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_media.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_modal.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/utilities/_align.scss","../../scss/mixins/_background-variant.scss","../../scss/utilities/_background.scss","../../scss/utilities/_borders.scss","../../scss/mixins/_clearfix.scss","../../scss/utilities/_display.scss","../../scss/utilities/_embed.scss","../../scss/utilities/_flex.scss","../../scss/utilities/_float.scss","../../scss/mixins/_float.scss","../../scss/utilities/_position.scss","../../scss/utilities/_screenreaders.scss","../../scss/mixins/_screen-reader.scss","../../scss/utilities/_shadows.scss","../../scss/utilities/_sizing.scss","../../scss/utilities/_spacing.scss","../../scss/utilities/_text.scss","../../scss/mixins/_text-truncate.scss","../../scss/mixins/_text-emphasis.scss","../../scss/mixins/_text-hide.scss","../../scss/utilities/_visibility.scss","../../scss/mixins/_visibility.scss","../../scss/_print.scss"],"names":[],"mappings":"AAAA;;;;;ACAA,MAGI,OAAA,QAAA,SAAA,QAAA,SAAA,QAAA,OAAA,QAAA,MAAA,QAAA,SAAA,QAAA,SAAA,QAAA,QAAA,QAAA,OAAA,QAAA,OAAA,QAAA,QAAA,KAAA,OAAA,QAAA,YAAA,QAIA,UAAA,QAAA,YAAA,QAAA,UAAA,QAAA,OAAA,QAAA,UAAA,QAAA,SAAA,QAAA,QAAA,QAAA,OAAA,QAIA,gBAAA,EAAA,gBAAA,MAAA,gBAAA,MAAA,gBAAA,MAAA,gBAAA,OAKF,yBAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,kBACA,wBAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UCGF,ECmBA,QADA,SDfE,WAAA,WAGF,KACE,YAAA,WACA,YAAA,KACA,yBAAA,KACA,qBAAA,KACA,mBAAA,UACA,4BAAA,YAKA,cACE,MAAA,aAMJ,QAAA,MAAA,WAAA,OAAA,OAAA,OAAA,OAAA,KAAA,IAAA,QACE,QAAA,MAWF,KACE,OAAA,EACA,YAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,kBACA,UAAA,KACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,WAAA,KACA,iBAAA,KEOF,sBFEE,QAAA,YASF,GACE,WAAA,YACA,OAAA,EACA,SAAA,QAaF,GAAA,GAAA,GAAA,GAAA,GAAA,GACE,WAAA,EACA,cAAA,MAQF,EACE,WAAA,EACA,cAAA,KClBF,0BD4BA,YAEE,gBAAA,UACA,wBAAA,UAAA,OAAA,gBAAA,UAAA,OACA,OAAA,KACA,cAAA,EAGF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QCvBF,GD0BA,GC3BA,GD8BE,WAAA,EACA,cAAA,KAGF,MC1BA,MACA,MAFA,MD+BE,cAAA,EAGF,GACE,YAAA,IAGF,GACE,cAAA,MACA,YAAA,EAGF,WACE,OAAA,EAAA,EAAA,KAGF,IACE,WAAA,OAIF,EC5BA,OD8BE,YAAA,OAIF,MACE,UAAA,IAQF,IClCA,IDoCE,SAAA,SACA,UAAA,IACA,YAAA,EACA,eAAA,SAGF,IAAM,OAAA,OACN,IAAM,IAAA,MAON,EACE,MAAA,QACA,gBAAA,KACA,iBAAA,YACA,6BAAA,QG7LA,QHgME,MAAA,QACA,gBAAA,UAUJ,8BACE,MAAA,QACA,gBAAA,KGzMA,oCAAA,oCH4ME,MAAA,QACA,gBAAA,KANJ,oCAUI,QAAA,ECpCJ,KACA,ID4CA,IC3CA,KD+CE,YAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UACA,UAAA,IAGF,IAEE,WAAA,EAEA,cAAA,KAEA,SAAA,KAGA,mBAAA,UAQF,OAEE,OAAA,EAAA,EAAA,KAQF,IACE,eAAA,OACA,aAAA,KAGF,eACE,SAAA,OAQF,MACE,gBAAA,SAGF,QACE,YAAA,OACA,eAAA,OACA,MAAA,QACA,WAAA,KACA,aAAA,OAGF,GAGE,WAAA,QAQF,MAEE,QAAA,aACA,cAAA,MAMF,OACE,cAAA,EAOF,aACE,QAAA,IAAA,OACA,QAAA,IAAA,KAAA,yBC9EF,ODiFA,MC/EA,SADA,OAEA,SDmFE,OAAA,EACA,YAAA,QACA,UAAA,QACA,YAAA,QAGF,OCjFA,MDmFE,SAAA,QAGF,OCjFA,ODmFE,eAAA,KC7EF,aACA,cDkFA,OCpFA,mBDwFE,mBAAA,OCjFF,gCACA,+BACA,gCDmFA,yBAIE,QAAA,EACA,aAAA,KClFF,qBDqFA,kBAEE,WAAA,WACA,QAAA,EAIF,iBCrFA,2BACA,kBAFA,iBD+FE,mBAAA,QAGF,SACE,SAAA,KAEA,OAAA,SAGF,SAME,UAAA,EAEA,QAAA,EACA,OAAA,EACA,OAAA,EAKF,OACE,QAAA,MACA,MAAA,KACA,UAAA,KACA,QAAA,EACA,cAAA,MACA,UAAA,OACA,YAAA,QACA,MAAA,QACA,YAAA,OAGF,SACE,eAAA,SEnGF,yCDEA,yCDuGE,OAAA,KEpGF,cF4GE,eAAA,KACA,mBAAA,KExGF,4CDEA,yCD+GE,mBAAA,KAQF,6BACE,KAAA,QACA,mBAAA,OAOF,OACE,QAAA,aAGF,QACE,QAAA,UACA,OAAA,QAGF,SACE,QAAA,KErHF,SF2HE,QAAA,eCrHF,IAAK,IAAK,IAAK,IAAK,IAAK,IGnWzB,GAAA,GAAA,GAAA,GAAA,GAAA,GAEE,cAAA,MACA,YAAA,QACA,YAAA,IACA,YAAA,IACA,MAAA,QAGF,IAAA,GAAU,UAAA,OACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,QACV,IAAA,GAAU,UAAA,OACV,IAAA,GAAU,UAAA,QACV,IAAA,GAAU,UAAA,KAEV,MACE,UAAA,QACA,YAAA,IAIF,WACE,UAAA,KACA,YAAA,IACA,YAAA,IAEF,WACE,UAAA,OACA,YAAA,IACA,YAAA,IAEF,WACE,UAAA,OACA,YAAA,IACA,YAAA,IAEF,WACE,UAAA,OACA,YAAA,IACA,YAAA,IJoCF,GI3BE,WAAA,KACA,cAAA,KACA,OAAA,EACA,WAAA,IAAA,MAAA,eHoXF,OG5WA,MAEE,UAAA,IACA,YAAA,IH+WF,MG5WA,KAEE,QAAA,KACA,iBAAA,QAQF,eC/EE,aAAA,EACA,WAAA,KDmFF,aCpFE,aAAA,EACA,WAAA,KDsFF,kBACE,QAAA,aADF,mCAII,aAAA,MAUJ,YACE,UAAA,IACA,eAAA,UAIF,YACE,cAAA,KACA,UAAA,QAGF,mBACE,QAAA,MACA,UAAA,IACA,MAAA,QAHF,2BAMI,QAAA,cEnHJ,WCIE,UAAA,KAGA,OAAA,KDDF,eACE,QAAA,OACA,iBAAA,KACA,OAAA,IAAA,MAAA,QEZE,cAAA,ODOF,UAAA,KAGA,OAAA,KDcF,QAEE,QAAA,aAGF,YACE,cAAA,MACA,YAAA,EAGF,gBACE,UAAA,IACA,MAAA,QGvCF,KACE,UAAA,MACA,MAAA,QACA,WAAA,WAGA,OACE,MAAA,QAKJ,IACE,QAAA,MAAA,MACA,UAAA,MACA,MAAA,KACA,iBAAA,QDbE,cAAA,MCSJ,QASI,QAAA,EACA,UAAA,KACA,YAAA,IT0NJ,ISnNE,QAAA,MACA,UAAA,MACA,MAAA,QAHF,SAOI,UAAA,QACA,MAAA,QACA,WAAA,OAKJ,gBACE,WAAA,MACA,WAAA,OCzCA,WCAA,MAAA,KACA,cAAA,KACA,aAAA,KACA,aAAA,KACA,YAAA,KCmDE,yBFvDF,WCYI,UAAA,OC2CF,yBFvDF,WCYI,UAAA,OC2CF,yBFvDF,WCYI,UAAA,OC2CF,0BFvDF,WCYI,UAAA,QDAJ,iBCZA,MAAA,KACA,cAAA,KACA,aAAA,KACA,aAAA,KACA,YAAA,KDkBA,KCJA,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,aAAA,MACA,YAAA,MDOA,YACE,aAAA,EACA,YAAA,EAFF,iBT0jBF,0BSpjBM,cAAA,EACA,aAAA,EGjCJ,KAAA,OAAA,QAAA,QAAA,QAAA,OAAA,OAAA,OAAA,OAAA,OAAA,OAAA,OAAA,OZ0lBF,UAEqJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACtG,aAFqJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACtG,aAFkJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,aAEqJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACtG,aY7lBI,SAAA,SACA,MAAA,KACA,WAAA,IACA,cAAA,KACA,aAAA,KAmBE,KACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,UACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,OFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,QFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,QFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,QFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,aAAwB,eAAA,GAAA,MAAA,GAExB,YAAuB,eAAA,GAAA,MAAA,GAGrB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,UAAwB,eAAA,GAAA,MAAA,GAAxB,UAAwB,eAAA,GAAA,MAAA,GAAxB,UAAwB,eAAA,GAAA,MAAA,GAMtB,UFTR,YAAA,UESQ,UFTR,YAAA,WESQ,UFTR,YAAA,IESQ,UFTR,YAAA,WESQ,UFTR,YAAA,WESQ,UFTR,YAAA,IESQ,UFTR,YAAA,WESQ,UFTR,YAAA,WESQ,UFTR,YAAA,IESQ,WFTR,YAAA,WESQ,WFTR,YAAA,WCUE,yBC7BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YCUE,yBC7BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YCUE,yBC7BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YCUE,0BC7BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YG9CF,OACE,MAAA,KACA,UAAA,KACA,cAAA,KACA,iBAAA,Yb2+CF,Ua/+CA,UAQI,QAAA,OACA,eAAA,IACA,WAAA,IAAA,MAAA,QAVJ,gBAcI,eAAA,OACA,cAAA,IAAA,MAAA,QAfJ,mBAmBI,WAAA,IAAA,MAAA,QAnBJ,cAuBI,iBAAA,Kb4+CJ,aan+CA,aAGI,QAAA,MASJ,gBACE,OAAA,IAAA,MAAA,Qb+9CF,mBah+CA,mBAKI,OAAA,IAAA,MAAA,Qbg+CJ,yBar+CA,yBAWM,oBAAA,Ibi+CN,8BAFA,qBa19CA,qBb29CA,2Bat9CI,OAAA,EAQJ,yCAEI,iBAAA,gBXnEF,4BW+EI,iBAAA,iBCtFJ,edqiDF,kBADA,kBchiDM,iBAAA,QZGJ,kCYQM,iBAAA,QALN,qCdoiDF,qCc3hDU,iBAAA,QAnBR,iBdojDF,oBADA,oBc/iDM,iBAAA,QZGJ,oCYQM,iBAAA,QALN,uCdmjDF,uCc1iDU,iBAAA,QAnBR,edmkDF,kBADA,kBc9jDM,iBAAA,QZGJ,kCYQM,iBAAA,QALN,qCdkkDF,qCczjDU,iBAAA,QAnBR,YdklDF,eADA,ec7kDM,iBAAA,QZGJ,+BYQM,iBAAA,QALN,kCdilDF,kCcxkDU,iBAAA,QAnBR,edimDF,kBADA,kBc5lDM,iBAAA,QZGJ,kCYQM,iBAAA,QALN,qCdgmDF,qCcvlDU,iBAAA,QAnBR,cdgnDF,iBADA,iBc3mDM,iBAAA,QZGJ,iCYQM,iBAAA,QALN,oCd+mDF,oCctmDU,iBAAA,QAnBR,ad+nDF,gBADA,gBc1nDM,iBAAA,QZGJ,gCYQM,iBAAA,QALN,mCd8nDF,mCcrnDU,iBAAA,QAnBR,Yd8oDF,eADA,eczoDM,iBAAA,QZGJ,+BYQM,iBAAA,QALN,kCd6oDF,kCcpoDU,iBAAA,QAnBR,cd6pDF,iBADA,iBcxpDM,iBAAA,iBZGJ,iCYQM,iBAAA,iBALN,oCd4pDF,oCcnpDU,iBAAA,iBD0FV,sBAGM,MAAA,KACA,iBAAA,QACA,aAAA,QALN,uBAWM,MAAA,QACA,iBAAA,QACA,aAAA,QAKN,YACE,MAAA,KACA,iBAAA,Qb2jDF,ea7jDA,eb8jDA,qBavjDI,aAAA,QAPJ,2BAWI,OAAA,EAXJ,oDAgBM,iBAAA,sBXxIJ,uCW+IM,iBAAA,uBFlFJ,4BEmGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GF7GN,4BEmGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GF7GN,4BEmGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GF7GN,6BEmGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GAfV,kBAOQ,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBAXR,kCAeU,OAAA,EEhLV,cACE,QAAA,MACA,MAAA,KACA,QAAA,QAAA,OACA,UAAA,KACA,YAAA,IACA,MAAA,QACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,QAKE,cAAA,OCfE,WAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAIJ,kDDHF,cCII,WAAA,MDJJ,0BAyBI,iBAAA,YACA,OAAA,EEnBF,oBACE,MAAA,QACA,iBAAA,KACA,aAAA,QACA,QAAA,EAKE,WAAA,EAAA,EAAA,EAAA,MAAA,oBFhBN,yCAkCI,MAAA,QAEA,QAAA,EApCJ,gCAkCI,MAAA,QAEA,QAAA,EApCJ,oCAkCI,MAAA,QAEA,QAAA,EApCJ,qCAkCI,MAAA,QAEA,QAAA,EApCJ,2BAkCI,MAAA,QAEA,QAAA,EApCJ,uBAAA,wBA8CI,iBAAA,QAEA,QAAA,EAIJ,gDAEI,OAAA,oBAFJ,qCAWI,MAAA,QACA,iBAAA,KAKJ,mBfgxDA,oBe9wDE,QAAA,MACA,MAAA,KAUF,gBACE,YAAA,oBACA,eAAA,oBACA,cAAA,EACA,UAAA,QACA,YAAA,IAGF,mBACE,YAAA,kBACA,eAAA,kBACA,UAAA,QACA,YAAA,IAGF,mBACE,YAAA,mBACA,eAAA,mBACA,UAAA,QACA,YAAA,IASF,wBACE,QAAA,MACA,MAAA,KACA,YAAA,QACA,eAAA,QACA,cAAA,EACA,YAAA,IACA,MAAA,QACA,iBAAA,YACA,OAAA,MAAA,YACA,aAAA,IAAA,EfywDmE,wCenxDrE,wCfmxD8G,qDAI9G,gEAFA,6EACA,iEAFA,8EepxDA,qDfmxDA,gEAFA,6EACA,iEAFA,8EelwDI,cAAA,EACA,aAAA,EAaJ,iBAAA,8BfowDA,yCAFA,sDACA,0CAFA,uDehwDE,QAAA,OAAA,MACA,UAAA,QACA,YAAA,IR/IE,cAAA,MP25DJ,2EAFA,wFACA,4EAFA,yFerwDA,gEAAA,mDAEI,OAAA,sBAIJ,iBAAA,8Bf0wDA,yCAFA,sDACA,0CAFA,uDetwDE,QAAA,MAAA,KACA,UAAA,QACA,YAAA,IR5JE,cAAA,MP86DJ,2EAFA,wFACA,4EAFA,yFe3wDA,gEAAA,mDAEI,OAAA,qBAUJ,YACE,cAAA,KAGF,WACE,QAAA,MACA,WAAA,OAQF,UACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,aAAA,KACA,YAAA,KAJF,ef2wDA,wBenwDI,cAAA,IACA,aAAA,IASJ,YACE,SAAA,SACA,QAAA,MACA,aAAA,QAGF,kBACE,SAAA,SACA,WAAA,MACA,YAAA,SAHF,6CAMI,MAAA,QAIJ,kBACE,cAAA,EAGF,mBACE,QAAA,mBAAA,QAAA,YACA,eAAA,OAAA,YAAA,OACA,aAAA,EACA,aAAA,OAJF,qCAQI,SAAA,OACA,WAAA,EACA,aAAA,SACA,YAAA,EElNF,gBACE,QAAA,KACA,MAAA,KACA,WAAA,OACA,UAAA,IACA,MAAA,QAGF,eACE,SAAA,SACA,IAAA,KACA,QAAA,EACA,QAAA,KACA,UAAA,KACA,QAAA,MACA,WAAA,MACA,UAAA,QACA,YAAA,EACA,MAAA,KACA,iBAAA,mBACA,cAAA,MjB09DJ,wBiBr9DI,uBAAA,oCAAA,mCAEE,aAAA,QjBy9DN,8BiB39DI,6BAAA,0CAAA,yCAKI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBjBg+DR,wCACA,uCANqD,uCACrD,sCAAyC,oDAEzC,mDiBp+DI,mDjBi+DJ,kDiBt9DQ,QAAA,MjBi+DkD,4CAC1D,2CiB59DI,wDjB29DJ,uDiBv9DQ,QAAA,MAMJ,6CAAA,yDAGI,MAAA,QjBw9DiD,2CACzD,0CiB59DI,uDjB29DJ,sDiBn9DQ,QAAA,MAMJ,qDAAA,iEAGI,MAAA,QAHJ,6DAAA,yEAMM,iBAAA,QjBq9DmD,+CAC7D,8CiB59DI,2DjB29DJ,0DiB/8DQ,QAAA,MAZJ,qEAAA,iFCzFA,iBAAA,QDyFA,mEAAA,+EAuBM,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAQN,+CAAA,2DAGI,aAAA,QAHJ,uDAAA,mEAKgB,aAAA,QjB68DsC,4CAC1D,2CiBn9DI,wDjBk9DJ,uDiBx8DQ,QAAA,MAVJ,qDAAA,iEAeM,WAAA,EAAA,EAAA,EAAA,MAAA,oBAhHR,kBACE,QAAA,KACA,MAAA,KACA,WAAA,OACA,UAAA,IACA,MAAA,QAGF,iBACE,SAAA,SACA,IAAA,KACA,QAAA,EACA,QAAA,KACA,UAAA,KACA,QAAA,MACA,WAAA,MACA,UAAA,QACA,YAAA,EACA,MAAA,KACA,iBAAA,mBACA,cAAA,MjBikEJ,0BiB5jEI,yBAAA,sCAAA,qCAEE,aAAA,QjBgkEN,gCiBlkEI,+BAAA,4CAAA,2CAKI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBjBukER,4CACA,2CANyD,2CACzD,0CAA6C,wDAE7C,uDiB3kEI,uDjBwkEJ,sDiB7jEQ,QAAA,MjBwkEsD,gDAC9D,+CiBnkEI,4DjBkkEJ,2DiB9jEQ,QAAA,MAMJ,+CAAA,2DAGI,MAAA,QjB+jEqD,+CAC7D,8CiBnkEI,2DjBkkEJ,0DiB1jEQ,QAAA,MAMJ,uDAAA,mEAGI,MAAA,QAHJ,+DAAA,2EAMM,iBAAA,QjB4jEuD,mDACjE,kDiBnkEI,+DjBkkEJ,8DiBtjEQ,QAAA,MAZJ,uEAAA,mFCzFA,iBAAA,QDyFA,qEAAA,iFAuBM,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAQN,iDAAA,6DAGI,aAAA,QAHJ,yDAAA,qEAKgB,aAAA,QjBojE0C,gDAC9D,+CiB1jEI,4DjByjEJ,2DiB/iEQ,QAAA,MAVJ,uDAAA,mEAeM,WAAA,EAAA,EAAA,EAAA,MAAA,oBF0HV,aACE,QAAA,YAAA,QAAA,KACA,cAAA,IAAA,KAAA,UAAA,IAAA,KACA,eAAA,OAAA,YAAA,OAHF,yBASI,MAAA,KJpNA,yBI2MJ,mBAeM,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OACA,cAAA,EAlBN,yBAuBM,QAAA,YAAA,QAAA,KACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,cAAA,IAAA,KAAA,UAAA,IAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,EA3BN,2BAgCM,QAAA,aACA,MAAA,KACA,eAAA,OAlCN,qCAuCM,QAAA,af67DJ,4Bep+DF,0BA4CM,MAAA,KA5CN,yBAkDM,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OACA,MAAA,KACA,aAAA,EAtDN,+BAyDM,SAAA,SACA,WAAA,EACA,aAAA,OACA,YAAA,EA5DN,6BAgEM,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OAjEN,mCAoEM,cAAA,GIrUN,KACE,QAAA,aACA,YAAA,IACA,WAAA,OACA,YAAA,OACA,eAAA,OACA,oBAAA,KAAA,iBAAA,KAAA,gBAAA,KAAA,YAAA,KACA,OAAA,IAAA,MAAA,YCsFA,QAAA,QAAA,OACA,UAAA,KACA,YAAA,IAGE,cAAA,OJnGE,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAIJ,kDGHF,KHII,WAAA,MdMF,WAAA,WiBGE,gBAAA,KAbJ,WAAA,WAkBI,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBAnBJ,cAAA,cAyBI,QAAA,IAzBJ,mCA+BI,OAAA,QA/BJ,0CAAA,0CAoCI,iBAAA,KAUJ,enBmwEA,wBmBjwEE,eAAA,KASA,aCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,mBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,mBAAA,mBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,mBAKJ,sBAAA,sBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,kDAAA,kDpB+yEF,mCoB5yEI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,wDAAA,wDpB4yEJ,yCoBvyEQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDaN,eCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,qBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,qBAAA,qBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,qBAKJ,wBAAA,wBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,oDAAA,oDpBi1EF,qCoB90EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,0DAAA,0DpB80EJ,2CoBz0EQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDaN,aCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,mBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,mBAAA,mBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,mBAKJ,sBAAA,sBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,kDAAA,kDpBm3EF,mCoBh3EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,wDAAA,wDpBg3EJ,yCoB32EQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDaN,UCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,gBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,gBAAA,gBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,oBAKJ,mBAAA,mBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,+CAAA,+CpBq5EF,gCoBl5EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,qDAAA,qDpBk5EJ,sCoB74EQ,WAAA,EAAA,EAAA,EAAA,MAAA,oBDaN,aCzDA,MAAA,QFAE,iBAAA,QEEF,aAAA,QlBIA,mBkBAE,MAAA,QFNA,iBAAA,QEQA,aAAA,QAGF,mBAAA,mBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,mBAKJ,sBAAA,sBAEE,MAAA,QACA,iBAAA,QACA,aAAA,QAGF,kDAAA,kDpBu7EF,mCoBp7EI,MAAA,QACA,iBAAA,QAIA,aAAA,QAEA,wDAAA,wDpBo7EJ,yCoB/6EQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDaN,YCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,kBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,kBAAA,kBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,mBAKJ,qBAAA,qBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,iDAAA,iDpBy9EF,kCoBt9EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,uDAAA,uDpBs9EJ,wCoBj9EQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDaN,WCzDA,MAAA,QFAE,iBAAA,QEEF,aAAA,QlBIA,iBkBAE,MAAA,QFNA,iBAAA,QEQA,aAAA,QAGF,iBAAA,iBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,qBAKJ,oBAAA,oBAEE,MAAA,QACA,iBAAA,QACA,aAAA,QAGF,gDAAA,gDpB2/EF,iCoBx/EI,MAAA,QACA,iBAAA,QAIA,aAAA,QAEA,sDAAA,sDpBw/EJ,uCoBn/EQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDaN,UCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,gBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,gBAAA,gBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,kBAKJ,mBAAA,mBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,+CAAA,+CpB6hFF,gCoB1hFI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,qDAAA,qDpB0hFJ,sCoBrhFQ,WAAA,EAAA,EAAA,EAAA,MAAA,kBDmBN,qBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,2BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,2BAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,8BAAA,8BAEE,MAAA,QACA,iBAAA,YAGF,0DAAA,0DpBmhFF,2CoBhhFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,gEAAA,gEpBmhFJ,iDoB9gFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDtBN,uBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,6BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,6BAAA,6BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,qBAGF,gCAAA,gCAEE,MAAA,QACA,iBAAA,YAGF,4DAAA,4DpBqjFF,6CoBljFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,kEAAA,kEpBqjFJ,mDoBhjFQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDtBN,qBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,2BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,2BAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,8BAAA,8BAEE,MAAA,QACA,iBAAA,YAGF,0DAAA,0DpBulFF,2CoBplFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,gEAAA,gEpBulFJ,iDoBllFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDtBN,kBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,wBACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,wBAAA,wBAEE,WAAA,EAAA,EAAA,EAAA,MAAA,oBAGF,2BAAA,2BAEE,MAAA,QACA,iBAAA,YAGF,uDAAA,uDpBynFF,wCoBtnFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,6DAAA,6DpBynFJ,8CoBpnFQ,WAAA,EAAA,EAAA,EAAA,MAAA,oBDtBN,qBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,2BACE,MAAA,QACA,iBAAA,QACA,aAAA,QAGF,2BAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,8BAAA,8BAEE,MAAA,QACA,iBAAA,YAGF,0DAAA,0DpB2pFF,2CoBxpFI,MAAA,QACA,iBAAA,QACA,aAAA,QAEA,gEAAA,gEpB2pFJ,iDoBtpFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDtBN,oBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,0BACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,0BAAA,0BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,6BAAA,6BAEE,MAAA,QACA,iBAAA,YAGF,yDAAA,yDpB6rFF,0CoB1rFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,+DAAA,+DpB6rFJ,gDoBxrFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDtBN,mBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,yBACE,MAAA,QACA,iBAAA,QACA,aAAA,QAGF,yBAAA,yBAEE,WAAA,EAAA,EAAA,EAAA,MAAA,qBAGF,4BAAA,4BAEE,MAAA,QACA,iBAAA,YAGF,wDAAA,wDpB+tFF,yCoB5tFI,MAAA,QACA,iBAAA,QACA,aAAA,QAEA,8DAAA,8DpB+tFJ,+CoB1tFQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDtBN,kBCZA,MAAA,QACA,iBAAA,YACA,iBAAA,KACA,aAAA,QAEA,wBACE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,wBAAA,wBAEE,WAAA,EAAA,EAAA,EAAA,MAAA,kBAGF,2BAAA,2BAEE,MAAA,QACA,iBAAA,YAGF,uDAAA,uDpBiwFF,wCoB9vFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,6DAAA,6DpBiwFJ,8CoB5vFQ,WAAA,EAAA,EAAA,EAAA,MAAA,kBDXR,UACE,YAAA,IACA,MAAA,QACA,iBAAA,YjBvEA,gBiB0EE,MAAA,QACA,gBAAA,UACA,iBAAA,YACA,aAAA,YATJ,gBAAA,gBAcI,gBAAA,UACA,aAAA,YACA,WAAA,KAhBJ,mBAAA,mBAqBI,MAAA,QACA,eAAA,KAWJ,mBAAA,QCdE,QAAA,MAAA,KACA,UAAA,QACA,YAAA,IAGE,cAAA,MDaJ,mBAAA,QClBE,QAAA,OAAA,MACA,UAAA,QACA,YAAA,IAGE,cAAA,MDsBJ,WACE,QAAA,MACA,MAAA,KAFF,sBAMI,WAAA,MnBwwFJ,6BADA,4BmBlwFA,6BAII,MAAA,KE3IJ,MLGM,WAAA,QAAA,KAAA,OAIJ,kDKPF,MLQI,WAAA,MKRJ,iBAII,QAAA,EAIJ,qBAEI,QAAA,KAIJ,YACE,SAAA,SACA,OAAA,EACA,SAAA,OLdI,WAAA,OAAA,KAAA,KAIJ,kDKOF,YLNI,WAAA,MhBu6FJ,UACA,UAFA,WsB/6FA,QAIE,SAAA,SCwBE,wBACE,QAAA,aACA,MAAA,EACA,OAAA,EACA,YAAA,OACA,eAAA,OACA,QAAA,GAlCJ,WAAA,KAAA,MACA,aAAA,KAAA,MAAA,YACA,cAAA,EACA,YAAA,KAAA,MAAA,YAyDE,8BACE,YAAA,EDhDN,eACE,SAAA,SACA,IAAA,KACA,KAAA,EACA,QAAA,KACA,QAAA,KACA,MAAA,KACA,UAAA,MACA,QAAA,MAAA,EACA,OAAA,QAAA,EAAA,EACA,UAAA,KACA,MAAA,QACA,WAAA,KACA,WAAA,KACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,gBf1BE,cAAA,Oe+BJ,qBACE,MAAA,EACA,KAAA,KAKF,uBAEI,IAAA,KACA,OAAA,KACA,WAAA,EACA,cAAA,QClBA,gCACE,QAAA,aACA,MAAA,EACA,OAAA,EACA,YAAA,OACA,eAAA,OACA,QAAA,GA3BJ,WAAA,EACA,aAAA,KAAA,MAAA,YACA,cAAA,KAAA,MACA,YAAA,KAAA,MAAA,YAkDE,sCACE,YAAA,EDPN,0BAEI,IAAA,EACA,MAAA,KACA,KAAA,KACA,WAAA,EACA,YAAA,QChCA,mCACE,QAAA,aACA,MAAA,EACA,OAAA,EACA,YAAA,OACA,eAAA,OACA,QAAA,GApBJ,WAAA,KAAA,MAAA,YACA,aAAA,EACA,cAAA,KAAA,MAAA,YACA,YAAA,KAAA,MA2CE,yCACE,YAAA,EAjCF,mCDsCE,eAAA,EAKN,yBAEI,IAAA,EACA,MAAA,KACA,KAAA,KACA,WAAA,EACA,aAAA,QCjDA,kCACE,QAAA,aACA,MAAA,EACA,OAAA,EACA,YAAA,OACA,eAAA,OACA,QAAA,GANF,kCAkBI,QAAA,KAGF,mCACE,QAAA,aACA,MAAA,EACA,OAAA,EACA,aAAA,OACA,eAAA,OACA,QAAA,GAlCN,WAAA,KAAA,MAAA,YACA,aAAA,KAAA,MACA,cAAA,KAAA,MAAA,YAqCE,wCACE,YAAA,EAZA,mCDkCA,eAAA,EAON,oCAAA,kCAAA,mCAAA,iCAKI,MAAA,KACA,OAAA,KAMJ,kBElGE,OAAA,EACA,OAAA,MAAA,EACA,SAAA,OACA,WAAA,IAAA,MAAA,QFsGF,eACE,QAAA,MACA,MAAA,KACA,QAAA,OAAA,OACA,MAAA,KACA,YAAA,IACA,MAAA,QACA,WAAA,QACA,YAAA,OACA,iBAAA,YACA,OAAA,EpBxGA,qBAAA,qBoB2GE,MAAA,QACA,gBAAA,KJtHA,iBAAA,QIwGJ,sBAAA,sBAoBI,MAAA,KACA,gBAAA,KJ7HA,iBAAA,QIwGJ,wBAAA,wBA2BI,MAAA,QACA,iBAAA,YAQJ,oBACE,QAAA,MAIF,iBACE,QAAA,MACA,QAAA,MAAA,OACA,cAAA,EACA,UAAA,QACA,MAAA,QACA,YAAA,OAIF,oBACE,QAAA,MACA,QAAA,OAAA,OACA,MAAA,QGjKF,WzBynGA,oByBvnGE,SAAA,SACA,QAAA,mBAAA,QAAA,YACA,eAAA,OzB6nGF,yByBjoGA,gBAOI,SAAA,SACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KzBgoGJ,+BE/nGE,sBuBII,QAAA,EzBkoGN,gCADA,gCADA,+ByB7oGA,uBAAA,uBAAA,sBAkBM,QAAA,EAlBN,qBzBopGA,2BACA,2BACA,iCACA,8BACA,oCACA,oCACA,0CyB/nGI,YAAA,KAKJ,aACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,cAAA,MAAA,gBAAA,WAHF,0BAMI,MAAA,KAIJ,4BAEI,YAAA,EzBooGJ,4CyBtoGA,uDlB5BI,wBAAA,EACA,2BAAA,EPuqGJ,6CyB5oGA,kClBdI,uBAAA,EACA,0BAAA,EkB0CJ,uBACE,cAAA,SACA,aAAA,SAFF,8BzB2nGA,yCADA,sCyBnnGI,YAAA,EAGF,yCACE,aAAA,EAIJ,0CAAA,+BACE,cAAA,QACA,aAAA,QAGF,0CAAA,+BACE,cAAA,OACA,aAAA,OAoBF,oBACE,mBAAA,OAAA,eAAA,OACA,eAAA,MAAA,YAAA,WACA,cAAA,OAAA,gBAAA,OAHF,yBzB6mGA,+ByBtmGI,MAAA,KAPJ,8BzBknGA,oCACA,oCACA,0CyBtmGI,WAAA,KACA,YAAA,EzB2mGJ,qDyB1nGA,gElB5FI,2BAAA,EACA,0BAAA,EP2tGJ,sDyBhoGA,2ClB1GI,uBAAA,EACA,wBAAA,EkBoJJ,uBzB2lGA,kCyBxlGI,cAAA,EzB6lGJ,4CyBhmGA,yCzBkmGA,uDADA,oDyB1lGM,SAAA,SACA,KAAA,cACA,eAAA,KClKN,aACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,eAAA,QAAA,YAAA,QACA,MAAA,K1BywGF,0BADA,4B0B7wGA,2BAUI,SAAA,SACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAGA,MAAA,GACA,cAAA,E1BywGJ,gCADA,kC0BvxGA,iCAmBM,QAAA,E1BixGN,uCADA,yCADA,wCADA,yCADA,2CADA,0CADA,wCADA,0C0B7xGA,yCAyBM,YAAA,K1BgxGN,6C0BzyGA,4CnBWI,wBAAA,EACA,2BAAA,EPmyGJ,8C0B/yGA,6CnByBI,uBAAA,EACA,0BAAA,EmB1BJ,0BAsCI,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OAvCJ,8D1B4zGA,qEOjzGI,wBAAA,EACA,2BAAA,EmBZJ,+DnByBI,uBAAA,EACA,0BAAA,EP6yGJ,oB0BjxGA,qBAEE,QAAA,YAAA,QAAA,K1BqxGF,yB0BvxGA,0BAQI,SAAA,SACA,QAAA,E1BuxGJ,8BACA,2CAEA,2CADA,wD0BlyGA,+B1B6xGA,4CAEA,4CADA,yD0B9wGI,YAAA,KAIJ,qBAAuB,aAAA,KACvB,oBAAsB,YAAA,KAQtB,kBACE,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,QAAA,QAAA,OACA,cAAA,EACA,UAAA,KACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,WAAA,OACA,YAAA,OACA,iBAAA,QACA,OAAA,IAAA,MAAA,QnBjGE,cAAA,OP03GJ,uC0BryGA,oCAkBI,WAAA,E1B2xGJ,wFACA,+EAHA,uDACA,oE0BxvGA,uC1BsvGA,oDOl3GI,wBAAA,EACA,2BAAA,EmBoIJ,sC1BuvGA,mDAGA,qEACA,kFAHA,yDACA,sEOh3GI,uBAAA,EACA,0BAAA,EoBvBJ,gBACE,SAAA,SACA,QAAA,MACA,WAAA,OACA,aAAA,OAGF,uBACE,QAAA,mBAAA,QAAA,YACA,aAAA,KAGF,sBACE,SAAA,SACA,QAAA,GACA,QAAA,EAHF,4DAMI,MAAA,KTrBA,iBAAA,QSeJ,0DAaI,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAbJ,2DAiBI,MAAA,KACA,iBAAA,QAlBJ,qDAwBM,MAAA,QAxBN,6DA2BQ,iBAAA,QAUR,sBACE,SAAA,SACA,cAAA,EAFF,8BAMI,SAAA,SACA,IAAA,OACA,KAAA,QACA,QAAA,MACA,MAAA,KACA,OAAA,KACA,eAAA,KACA,QAAA,GACA,oBAAA,KAAA,iBAAA,KAAA,gBAAA,KAAA,YAAA,KACA,iBAAA,QAfJ,6BAqBI,SAAA,SACA,IAAA,OACA,KAAA,QACA,QAAA,MACA,MAAA,KACA,OAAA,KACA,QAAA,GACA,kBAAA,UACA,oBAAA,OAAA,OACA,gBAAA,IAAA,IASJ,+CpB7FI,cAAA,OoB6FJ,6ET3FI,iBAAA,QS2FJ,4EAUM,iBAAA,yMAVN,mFT3FI,iBAAA,QS2FJ,kFAoBM,iBAAA,sJApBN,sFA0BM,iBAAA,mBA1BN,4FA6BM,iBAAA,mBASN,4CAEI,cAAA,IAFJ,0ETjII,iBAAA,QSiIJ,yEAUM,iBAAA,mJAVN,mFAgBM,iBAAA,mBAYN,eACE,QAAA,aACA,MAAA,KACA,OAAA,oBACA,QAAA,QAAA,QAAA,QAAA,OACA,YAAA,IACA,MAAA,QACA,eAAA,OACA,WAAA,KAAA,uKAAA,UAAA,MAAA,OAAA,OACA,gBAAA,IAAA,KACA,OAAA,IAAA,MAAA,QAEE,cAAA,OAIF,mBAAA,KAAA,gBAAA,KAAA,WAAA,KAhBF,qBAmBI,aAAA,QACA,QAAA,EACA,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,CAAA,EAAA,EAAA,IAAA,qBArBJ,gCA6BM,MAAA,QACA,iBAAA,KA9BN,yBAAA,qCAoCI,OAAA,KACA,cAAA,OACA,iBAAA,KAtCJ,wBA0CI,MAAA,QACA,iBAAA,QA3CJ,2BAgDI,QAAA,EAIJ,kBACE,OAAA,sBACA,YAAA,QACA,eAAA,QACA,UAAA,IAGF,kBACE,OAAA,qBACA,YAAA,QACA,eAAA,QACA,UAAA,KAQF,aACE,SAAA,SACA,QAAA,aACA,MAAA,KACA,OAAA,oBACA,cAAA,EAGF,mBACE,SAAA,SACA,QAAA,EACA,MAAA,KACA,OAAA,oBACA,OAAA,EACA,QAAA,EANF,4CASI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBAVJ,mDAaM,aAAA,QAbN,sDAmBM,QAAA,SAKN,mBACE,SAAA,SACA,IAAA,EACA,MAAA,EACA,KAAA,EACA,QAAA,EACA,OAAA,oBACA,QAAA,QAAA,OACA,YAAA,IACA,MAAA,QACA,iBAAA,KACA,OAAA,IAAA,MAAA,QpBjRE,cAAA,OoBsQJ,0BAgBI,SAAA,SACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,QAAA,EACA,QAAA,MACA,OAAA,QACA,QAAA,QAAA,OACA,YAAA,IACA,MAAA,QACA,QAAA,ST9RA,iBAAA,QSgSA,YAAA,IAAA,MAAA,QpBlSA,cAAA,EAAA,OAAA,OAAA,EoB6SJ,cACE,MAAA,KACA,aAAA,EACA,iBAAA,YACA,mBAAA,KAAA,gBAAA,KAAA,WAAA,KAJF,oBAOI,QAAA,EAPJ,gCAWI,OAAA,EAXJ,oCAeI,MAAA,KACA,OAAA,KACA,WAAA,QT5TA,iBAAA,QS8TA,OAAA,EpBhUA,cAAA,KoBmUA,mBAAA,KAAA,WAAA,KAtBJ,0CAyBM,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBA1BN,2CT3SI,iBAAA,QS2SJ,6CAmCI,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,QACA,aAAA,YpBrVA,cAAA,KoB6SJ,gCA8CI,MAAA,KACA,OAAA,KT1VA,iBAAA,QS4VA,OAAA,EpB9VA,cAAA,KoBiWA,gBAAA,KAAA,WAAA,KApDJ,sCAuDM,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAxDN,uCT3SI,iBAAA,QS2SJ,gCAiEI,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,QACA,aAAA,YpBnXA,cAAA,KoB6SJ,yBA4EI,MAAA,KACA,OAAA,KTxXA,iBAAA,QS0XA,OAAA,EpB5XA,cAAA,KoB+XA,WAAA,KAlFJ,+BAqFM,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAtFN,gCT3SI,iBAAA,QS2SJ,yBA+FI,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,YACA,aAAA,YACA,aAAA,MArGJ,8BA0GI,iBAAA,QpBvZA,cAAA,KoB6SJ,8BA+GI,aAAA,KACA,iBAAA,QpB7ZA,cAAA,KqBCJ,KACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,aAAA,EACA,cAAA,EACA,WAAA,KAGF,UACE,QAAA,MACA,QAAA,MAAA,K1BCA,gBAAA,gB0BEE,gBAAA,KALJ,mBAUI,MAAA,QAQJ,UACE,cAAA,IAAA,MAAA,QADF,oBAII,cAAA,KAJJ,oBAQI,OAAA,IAAA,MAAA,YrB7BA,uBAAA,OACA,wBAAA,OLKF,0BAAA,0B0B2BI,aAAA,QAAA,QAAA,QAZN,6BAgBM,MAAA,QACA,iBAAA,YACA,aAAA,Y5BsvHN,mC4BxwHA,2BAwBI,MAAA,QACA,iBAAA,KACA,aAAA,QAAA,QAAA,KA1BJ,yBA+BI,WAAA,KrBpDA,uBAAA,EACA,wBAAA,EqB8DJ,qBrBrEI,cAAA,OqBqEJ,4B5B+uHA,2B4BxuHI,MAAA,KACA,iBAAA,QASJ,oBAEI,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,WAAA,OAIJ,yBAEI,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,WAAA,OASJ,uBAEI,QAAA,KAFJ,qBAKI,QAAA,MClGJ,QACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,QAAA,gBAAA,cACA,QAAA,MAAA,KANF,mB7Bg1HA,yB6Bp0HI,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,QAAA,gBAAA,cASJ,cACE,QAAA,aACA,YAAA,SACA,eAAA,SACA,aAAA,KACA,UAAA,QACA,YAAA,QACA,YAAA,O3BhCA,oBAAA,oB2BmCE,gBAAA,KASJ,YACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,aAAA,EACA,cAAA,EACA,WAAA,KALF,sBAQI,cAAA,EACA,aAAA,EATJ,2BAaI,SAAA,OACA,MAAA,KASJ,aACE,QAAA,aACA,YAAA,MACA,eAAA,MAYF,iBACE,wBAAA,KAAA,WAAA,KACA,kBAAA,EAAA,UAAA,EAGA,eAAA,OAAA,YAAA,OAIF,gBACE,QAAA,OAAA,OACA,UAAA,QACA,YAAA,EACA,iBAAA,YACA,OAAA,IAAA,MAAA,YtB5GE,cAAA,OLYF,sBAAA,sB2BoGE,gBAAA,KATJ,8CAcI,OAAA,QAMJ,qBACE,QAAA,aACA,MAAA,MACA,OAAA,MACA,eAAA,OACA,QAAA,GACA,WAAA,UAAA,OAAA,OACA,gBAAA,KAAA,KlB7DE,4BkBuEC,6B7B0yHH,mC6BtyHQ,cAAA,EACA,aAAA,GlBzFN,yBkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7Bm0HH,mC6BtyHQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MlB/GN,4BkBuEC,6B7Bo1HH,mC6Bh1HQ,cAAA,EACA,aAAA,GlBzFN,yBkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7B62HH,mC6Bh1HQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MlB/GN,4BkBuEC,6B7B83HH,mC6B13HQ,cAAA,EACA,aAAA,GlBzFN,yBkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7Bu5HH,mC6B13HQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MlB/GN,6BkBuEC,6B7Bw6HH,mC6Bp6HQ,cAAA,EACA,aAAA,GlBzFN,0BkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7Bi8HH,mC6Bp6HQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MA7CV,eAeQ,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAhBR,0B7B69HA,gC6Bp9HU,cAAA,EACA,aAAA,EAVV,2BAmBU,mBAAA,IAAA,eAAA,IAnBV,0CAsBY,SAAA,SAtBZ,qCA0BY,cAAA,MACA,aAAA,MA3BZ,0B7Bi/HA,gC6B/8HU,cAAA,OAAA,UAAA,OAlCV,gCAsCU,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KAzCV,+BA6CU,QAAA,KAaV,4BAEI,MAAA,e3BvLF,kCAAA,kC2B0LI,MAAA,eALN,oCAWM,MAAA,e3BhMJ,0CAAA,0C2BmMM,MAAA,eAdR,6CAkBQ,MAAA,e7B08HR,4CAEA,2CADA,yC6B79HA,0CA0BM,MAAA,eA1BN,8BA+BI,MAAA,eACA,aAAA,eAhCJ,mCAoCI,iBAAA,oPApCJ,2BAwCI,MAAA,eAxCJ,6BA0CM,MAAA,e3B/NJ,mCAAA,mC2BkOM,MAAA,eAOR,2BAEI,MAAA,K3B3OF,iCAAA,iC2B8OI,MAAA,KALN,mCAWM,MAAA,qB3BpPJ,yCAAA,yC2BuPM,MAAA,sBAdR,4CAkBQ,MAAA,sB7Bs8HR,2CAEA,0CADA,wC6Bz9HA,yCA0BM,MAAA,KA1BN,6BA+BI,MAAA,qBACA,aAAA,qBAhCJ,kCAoCI,iBAAA,0PApCJ,0BAwCI,MAAA,qBAxCJ,4BA0CM,MAAA,K3BnRJ,kCAAA,kC2BsRM,MAAA,KClSR,MACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,UAAA,EACA,UAAA,WACA,iBAAA,KACA,gBAAA,WACA,OAAA,IAAA,MAAA,iBvBRE,cAAA,OuBAJ,SAYI,aAAA,EACA,YAAA,EAbJ,2DvBMI,uBAAA,OACA,wBAAA,OuBPJ,yDvBoBI,2BAAA,OACA,0BAAA,OuBQJ,WAGE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,QAAA,QAGF,YACE,cAAA,OAGF,eACE,WAAA,SACA,cAAA,EAGF,sBACE,cAAA,E5BtCA,iB4B2CE,gBAAA,KAFJ,sBAMI,YAAA,QAQJ,aACE,QAAA,OAAA,QACA,cAAA,EACA,iBAAA,gBACA,cAAA,IAAA,MAAA,iBAJF,yBvB/DI,cAAA,mBAAA,mBAAA,EAAA,EuB+DJ,sDAYM,WAAA,EAKN,aACE,QAAA,OAAA,QACA,iBAAA,gBACA,WAAA,IAAA,MAAA,iBAHF,wBvBhFI,cAAA,EAAA,EAAA,mBAAA,mBuB+FJ,kBACE,aAAA,SACA,cAAA,QACA,YAAA,SACA,cAAA,EAGF,mBACE,aAAA,SACA,YAAA,SAIF,kBACE,SAAA,SACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,QAGF,UACE,MAAA,KvBtHE,cAAA,mBuB2HJ,cACE,MAAA,KvBtHE,uBAAA,mBACA,wBAAA,mBuByHJ,iBACE,MAAA,KvB7GE,2BAAA,mBACA,0BAAA,mBuBmHJ,WACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OAFF,iBAKI,cAAA,KnBrFA,yBmBgFJ,WASI,cAAA,IAAA,KAAA,UAAA,IAAA,KACA,aAAA,MACA,YAAA,MAXJ,iBAcM,QAAA,YAAA,QAAA,KAEA,SAAA,EAAA,EAAA,GAAA,KAAA,EAAA,EAAA,GACA,mBAAA,OAAA,eAAA,OACA,aAAA,KACA,cAAA,EACA,YAAA,MAUN,YACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OAFF,kBAOI,cAAA,KnBrHA,yBmB8GJ,YAWI,cAAA,IAAA,KAAA,UAAA,IAAA,KAXJ,kBAgBM,SAAA,EAAA,EAAA,GAAA,KAAA,EAAA,EAAA,GACA,cAAA,EAjBN,wBAoBQ,YAAA,EACA,YAAA,EArBR,8BvBzJI,wBAAA,EACA,2BAAA,EP84IF,2C8BtvIF,4CA+BY,wBAAA,E9B2tIV,2C8B1vIF,+CAmCY,2BAAA,EAnCZ,6BvB3II,uBAAA,EACA,0BAAA,EP44IF,0C8BlwIF,2CA4CY,uBAAA,E9B0tIV,0C8BtwIF,8CAgDY,0BAAA,EAhDZ,6BvBtKI,cAAA,OPm7IF,0C8B7wIF,2CvBhKI,uBAAA,OACA,wBAAA,OPi7IF,0C8BlxIF,8CvBlJI,2BAAA,OACA,0BAAA,OuBiJJ,sEvBtKI,cAAA,EPk8IF,mFADA,mFADA,uF8B1xIF,oFvBtKI,cAAA,GuB2PJ,oBAEI,cAAA,OnBrMA,yBmBmMJ,cAMI,qBAAA,EAAA,kBAAA,EAAA,aAAA,EACA,mBAAA,QAAA,gBAAA,QAAA,WAAA,QACA,QAAA,EACA,OAAA,EATJ,oBAYM,QAAA,aACA,MAAA,MAUN,wDAEI,cAAA,EACA,cAAA,EAHJ,8DAQM,cAAA,EARN,+BAaI,cAAA,EACA,2BAAA,EACA,0BAAA,EAfJ,8BAmBI,uBAAA,EACA,wBAAA,EC1SJ,YACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,QAAA,OAAA,KACA,cAAA,KACA,WAAA,KACA,iBAAA,QxBFE,cAAA,OwBMJ,kCAGI,aAAA,MAHJ,0CAMM,QAAA,aACA,cAAA,MACA,MAAA,QACA,QAAA,IATN,gDAoBI,gBAAA,UApBJ,gDAwBI,gBAAA,KAxBJ,wBA4BI,MAAA,QCtCJ,YACE,QAAA,YAAA,QAAA,K5BGA,aAAA,EACA,WAAA,KGDE,cAAA,OyBEJ,WACE,SAAA,SACA,QAAA,MACA,QAAA,MAAA,OACA,YAAA,KACA,YAAA,KACA,MAAA,QACA,iBAAA,KACA,OAAA,IAAA,MAAA,QARF,iBAWI,QAAA,EACA,MAAA,QACA,gBAAA,KACA,iBAAA,QACA,aAAA,QAfJ,iBAmBI,QAAA,EACA,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBArBJ,yCA0BI,OAAA,QAIJ,kCAGM,YAAA,EzBRF,uBAAA,OACA,0BAAA,OyBIJ,iCzBnBI,wBAAA,OACA,2BAAA,OyBkBJ,6BAcI,QAAA,EACA,MAAA,KACA,iBAAA,QACA,aAAA,QAjBJ,+BAqBI,MAAA,QACA,eAAA,KAEA,OAAA,KACA,iBAAA,KACA,aAAA,QC3DF,0BACE,QAAA,OAAA,OACA,UAAA,QACA,YAAA,IAKE,iD1BoBF,uBAAA,MACA,0BAAA,M0BhBE,gD1BCF,wBAAA,MACA,2BAAA,M0BfF,0BACE,QAAA,OAAA,MACA,UAAA,QACA,YAAA,IAKE,iD1BoBF,uBAAA,MACA,0BAAA,M0BhBE,gD1BCF,wBAAA,MACA,2BAAA,M2BbJ,OACE,QAAA,aACA,QAAA,MAAA,KACA,UAAA,IACA,YAAA,IACA,YAAA,EACA,WAAA,OACA,YAAA,OACA,eAAA,S3BTE,cAAA,O2BCJ,aAaI,QAAA,KAKJ,YACE,SAAA,SACA,IAAA,KAOF,YACE,cAAA,KACA,aAAA,K3B9BE,cAAA,M2BuCF,eC1CA,MAAA,KACA,iBAAA,QjCcA,2BAAA,2BiCVI,MAAA,KACA,gBAAA,KACA,iBAAA,QDmCJ,iBC1CA,MAAA,KACA,iBAAA,QjCcA,6BAAA,6BiCVI,MAAA,KACA,gBAAA,KACA,iBAAA,QDmCJ,eC1CA,MAAA,KACA,iBAAA,QjCcA,2BAAA,2BiCVI,MAAA,KACA,gBAAA,KACA,iBAAA,QDmCJ,YC1CA,MAAA,KACA,iBAAA,QjCcA,wBAAA,wBiCVI,MAAA,KACA,gBAAA,KACA,iBAAA,QDmCJ,eC1CA,MAAA,QACA,iBAAA,QjCcA,2BAAA,2BiCVI,MAAA,QACA,gBAAA,KACA,iBAAA,QDmCJ,cC1CA,MAAA,KACA,iBAAA,QjCcA,0BAAA,0BiCVI,MAAA,KACA,gBAAA,KACA,iBAAA,QDmCJ,aC1CA,MAAA,QACA,iBAAA,QjCcA,yBAAA,yBiCVI,MAAA,QACA,gBAAA,KACA,iBAAA,QDmCJ,YC1CA,MAAA,KACA,iBAAA,QjCcA,wBAAA,wBiCVI,MAAA,KACA,gBAAA,KACA,iBAAA,QCRN,WACE,QAAA,KAAA,KACA,cAAA,KACA,iBAAA,Q7BCE,cAAA,MIwDA,yByB5DJ,WAOI,QAAA,KAAA,MAIJ,iBACE,cAAA,EACA,aAAA,E7BTE,cAAA,E8BAJ,OACE,SAAA,SACA,QAAA,OAAA,QACA,cAAA,KACA,OAAA,IAAA,MAAA,Y9BJE,cAAA,O8BSJ,eAEE,MAAA,QAIF,YACE,YAAA,IAQF,mBACE,cAAA,KADF,0BAKI,SAAA,SACA,IAAA,EACA,MAAA,EACA,QAAA,OAAA,QACA,MAAA,QAUF,eC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,kBACE,iBAAA,QAGF,2BACE,MAAA,QDqCF,iBC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,oBACE,iBAAA,QAGF,6BACE,MAAA,QDqCF,eC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,kBACE,iBAAA,QAGF,2BACE,MAAA,QDqCF,YC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,eACE,iBAAA,QAGF,wBACE,MAAA,QDqCF,eC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,kBACE,iBAAA,QAGF,2BACE,MAAA,QDqCF,cC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,iBACE,iBAAA,QAGF,0BACE,MAAA,QDqCF,aC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,gBACE,iBAAA,QAGF,yBACE,MAAA,QDqCF,YC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,eACE,iBAAA,QAGF,wBACE,MAAA,QCVJ,wCACE,KAAO,oBAAA,KAAA,EACP,GAAK,oBAAA,EAAA,GAFP,gCACE,KAAO,oBAAA,KAAA,EACP,GAAK,oBAAA,EAAA,GAGP,UACE,QAAA,YAAA,QAAA,KACA,OAAA,KACA,SAAA,OACA,UAAA,OACA,iBAAA,QhCNE,cAAA,OgCWJ,cACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,cAAA,OAAA,gBAAA,OACA,MAAA,KACA,WAAA,OACA,YAAA,OACA,iBAAA,QvBjBI,WAAA,MAAA,IAAA,KAIJ,kDuBMF,cvBLI,WAAA,MuBgBJ,sBrBiBE,iBAAA,iKqBfA,gBAAA,KAAA,KAGF,uBACE,kBAAA,qBAAA,GAAA,OAAA,SAAA,UAAA,qBAAA,GAAA,OAAA,SChCF,OACE,QAAA,YAAA,QAAA,KACA,eAAA,MAAA,YAAA,WAGF,YACE,SAAA,EAAA,KAAA,ECFF,YACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OAGA,aAAA,EACA,cAAA,EASF,wBACE,MAAA,KACA,MAAA,QACA,WAAA,QvCNA,8BAAA,8BuCUE,MAAA,QACA,gBAAA,KACA,iBAAA,QATJ,+BAaI,MAAA,QACA,iBAAA,QASJ,iBACE,SAAA,SACA,QAAA,MACA,QAAA,OAAA,QAEA,cAAA,KACA,iBAAA,KACA,OAAA,IAAA,MAAA,iBAPF,6BlChCI,uBAAA,OACA,wBAAA,OkC+BJ,4BAcI,cAAA,ElChCA,2BAAA,OACA,0BAAA,OLTF,uBAAA,uBuC6CE,QAAA,EACA,gBAAA,KApBJ,0BAAA,0BAyBI,MAAA,QACA,iBAAA,KA1BJ,wBA+BI,QAAA,EACA,MAAA,KACA,iBAAA,QACA,aAAA,QAUJ,mCAEI,aAAA,EACA,YAAA,ElCrFA,cAAA,EkCkFJ,2DASM,WAAA,EATN,yDAeM,cAAA,EClGJ,yBACE,MAAA,QACA,iBAAA,QxCWF,sDAAA,sDwCPM,MAAA,QACA,iBAAA,QAPN,uDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,2BACE,MAAA,QACA,iBAAA,QxCWF,wDAAA,wDwCPM,MAAA,QACA,iBAAA,QAPN,yDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,yBACE,MAAA,QACA,iBAAA,QxCWF,sDAAA,sDwCPM,MAAA,QACA,iBAAA,QAPN,uDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,sBACE,MAAA,QACA,iBAAA,QxCWF,mDAAA,mDwCPM,MAAA,QACA,iBAAA,QAPN,oDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,yBACE,MAAA,QACA,iBAAA,QxCWF,sDAAA,sDwCPM,MAAA,QACA,iBAAA,QAPN,uDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,wBACE,MAAA,QACA,iBAAA,QxCWF,qDAAA,qDwCPM,MAAA,QACA,iBAAA,QAPN,sDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,uBACE,MAAA,QACA,iBAAA,QxCWF,oDAAA,oDwCPM,MAAA,QACA,iBAAA,QAPN,qDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,sBACE,MAAA,QACA,iBAAA,QxCWF,mDAAA,mDwCPM,MAAA,QACA,iBAAA,QAPN,oDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QChBR,OACE,MAAA,MACA,UAAA,OACA,YAAA,IACA,YAAA,EACA,MAAA,KACA,YAAA,EAAA,IAAA,EAAA,KACA,QAAA,GzCSA,aAAA,ayCNE,MAAA,KACA,gBAAA,KACA,QAAA,IAZJ,qCAiBI,OAAA,QAUJ,aACE,QAAA,EACA,iBAAA,YACA,OAAA,EACA,mBAAA,KCxBF,YACE,SAAA,OAIF,OACE,SAAA,MACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,KACA,QAAA,KACA,SAAA,OAGA,QAAA,EAKA,mBACE,WAAA,OACA,WAAA,KAKJ,cACE,SAAA,SACA,MAAA,KACA,OAAA,MAEA,eAAA,KAGA,0B5BtCI,WAAA,kBAAA,IAAA,SAAA,WAAA,UAAA,IAAA,SAAA,WAAA,UAAA,IAAA,QAAA,CAAA,kBAAA,IAAA,S4BwCF,kBAAA,kBAAA,UAAA,kB5BpCF,kD4BkCA,0B5BjCE,WAAA,M4BqCF,0BACE,kBAAA,eAAA,UAAA,eAIJ,uBACE,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,WAAA,yBAIF,eACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,MAAA,KAEA,eAAA,KACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,erChEE,cAAA,MqCoEF,QAAA,EAIF,gBACE,SAAA,MACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,KACA,iBAAA,KAPF,qBAUW,QAAA,EAVX,qBAWW,QAAA,GAKX,cACE,QAAA,YAAA,QAAA,KACA,eAAA,MAAA,YAAA,WACA,cAAA,QAAA,gBAAA,cACA,QAAA,KACA,cAAA,IAAA,MAAA,QrCvFE,uBAAA,MACA,wBAAA,MqCiFJ,qBASI,QAAA,KAEA,OAAA,MAAA,MAAA,MAAA,KAKJ,aACE,cAAA,EACA,YAAA,IAKF,YACE,SAAA,SAGA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,QAAA,KAIF,cACE,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,IAAA,gBAAA,SACA,QAAA,KACA,WAAA,IAAA,MAAA,QALF,iCAQyB,YAAA,OARzB,gCASwB,aAAA,OAIxB,yBACE,SAAA,SACA,IAAA,QACA,MAAA,KACA,OAAA,KACA,SAAA,OjClFE,yBiCzBJ,cAkHI,UAAA,MACA,OAAA,QAAA,KAlGJ,uBAsGI,WAAA,2BAOF,UAAY,UAAA,OjCrGV,yBiC0GF,UAAY,UAAA,OCrKd,SACE,SAAA,SACA,QAAA,KACA,QAAA,MACA,OAAA,ECJA,YAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,kBAEA,WAAA,OACA,YAAA,IACA,YAAA,IACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,OACA,WAAA,OACA,aAAA,OACA,YAAA,OACA,WAAA,KDNA,UAAA,QAEA,UAAA,WACA,QAAA,EAXF,cAaW,QAAA,GAbX,gBAgBI,SAAA,SACA,QAAA,MACA,MAAA,MACA,OAAA,MAnBJ,wBAsBM,SAAA,SACA,QAAA,GACA,aAAA,YACA,aAAA,MAKN,mCAAA,gBACE,QAAA,MAAA,EADF,0CAAA,uBAII,OAAA,EAJJ,kDAAA,+BAOM,IAAA,EACA,aAAA,MAAA,MAAA,EACA,iBAAA,KAKN,qCAAA,kBACE,QAAA,EAAA,MADF,4CAAA,yBAII,KAAA,EACA,MAAA,MACA,OAAA,MANJ,oDAAA,iCASM,MAAA,EACA,aAAA,MAAA,MAAA,MAAA,EACA,mBAAA,KAKN,sCAAA,mBACE,QAAA,MAAA,EADF,6CAAA,0BAII,IAAA,EAJJ,qDAAA,kCAOM,OAAA,EACA,aAAA,EAAA,MAAA,MACA,oBAAA,KAKN,oCAAA,iBACE,QAAA,EAAA,MADF,2CAAA,wBAII,MAAA,EACA,MAAA,MACA,OAAA,MANJ,mDAAA,gCASM,KAAA,EACA,aAAA,MAAA,EAAA,MAAA,MACA,kBAAA,KAqBN,eACE,UAAA,MACA,QAAA,OAAA,MACA,MAAA,KACA,WAAA,OACA,iBAAA,KtC5GE,cAAA,OwCJJ,SACE,SAAA,SACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,QAAA,MACA,UAAA,MDLA,YAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,kBAEA,WAAA,OACA,YAAA,IACA,YAAA,IACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,OACA,WAAA,OACA,aAAA,OACA,YAAA,OACA,WAAA,KCLA,UAAA,QAEA,UAAA,WACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,exCXE,cAAA,MwCJJ,gBAoBI,SAAA,SACA,QAAA,MACA,MAAA,KACA,OAAA,MACA,OAAA,EAAA,MAxBJ,uBAAA,wBA4BM,SAAA,SACA,QAAA,MACA,QAAA,GACA,aAAA,YACA,aAAA,MAKN,mCAAA,gBACE,cAAA,MADF,0CAAA,uBAII,OAAA,yB/Cu9KJ,iD+C39KA,kD/C09KA,8B+C19KA,+BASI,aAAA,MAAA,MAAA,EATJ,kDAAA,+BAaI,OAAA,EACA,iBAAA,gB/Cw9KJ,iD+Ct+KA,8BAkBI,OAAA,IACA,iBAAA,KAIJ,qCAAA,kBACE,YAAA,MADF,4CAAA,yBAII,KAAA,yBACA,MAAA,MACA,OAAA,KACA,OAAA,MAAA,E/C09KJ,mD+Cj+KA,oD/Cg+KA,gC+Ch+KA,iCAYI,aAAA,MAAA,MAAA,MAAA,EAZJ,oDAAA,iCAgBI,KAAA,EACA,mBAAA,gB/C29KJ,mD+C5+KA,gCAqBI,KAAA,IACA,mBAAA,KAIJ,sCAAA,mBACE,WAAA,MADF,6CAAA,0BAII,IAAA,yB/C69KJ,oD+Cj+KA,qD/Cg+KA,iC+Ch+KA,kCASI,aAAA,EAAA,MAAA,MAAA,MATJ,qDAAA,kCAaI,IAAA,EACA,oBAAA,gB/C89KJ,oD+C5+KA,iCAkBI,IAAA,IACA,oBAAA,KAnBJ,8DAAA,2CAwBI,SAAA,SACA,IAAA,EACA,KAAA,IACA,QAAA,MACA,MAAA,KACA,YAAA,OACA,QAAA,GACA,cAAA,IAAA,MAAA,QAIJ,oCAAA,iBACE,aAAA,MADF,2CAAA,wBAII,MAAA,yBACA,MAAA,MACA,OAAA,KACA,OAAA,MAAA,E/C+9KJ,kD+Ct+KA,mD/Cq+KA,+B+Cr+KA,gCAYI,aAAA,MAAA,EAAA,MAAA,MAZJ,mDAAA,gCAgBI,MAAA,EACA,kBAAA,gB/Cg+KJ,kD+Cj/KA,+BAqBI,MAAA,IACA,kBAAA,KAqBJ,gBACE,QAAA,MAAA,OACA,cAAA,EACA,UAAA,KACA,MAAA,QACA,iBAAA,QACA,cAAA,IAAA,MAAA,QxChKE,uBAAA,kBACA,wBAAA,kBwCyJJ,sBAWI,QAAA,KAIJ,cACE,QAAA,MAAA,OACA,MAAA,QC3KF,UACE,SAAA,SAGF,gBACE,SAAA,SACA,MAAA,KACA,SAAA,OAGF,eACE,SAAA,SACA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,MAAA,KhCnBI,WAAA,kBAAA,IAAA,KAAA,WAAA,UAAA,IAAA,KAAA,WAAA,UAAA,IAAA,IAAA,CAAA,kBAAA,IAAA,KgCqBJ,4BAAA,OAAA,oBAAA,OACA,oBAAA,OAAA,YAAA,OhClBA,kDgCWF,ehCVI,WAAA,MhBypLJ,oBACA,oBgDtoLA,sBAGE,QAAA,MAGF,oBhDqoLA,oBgDnoLE,SAAA,SACA,IAAA,EAGF,uChDqoLA,wCgDnoLE,kBAAA,cAAA,UAAA,cAEwC,mFAJ1C,uChD4oLE,wCgDvoLE,kBAAA,mBAAA,UAAA,oBhD8oLJ,4BgD1oLA,oBAEE,kBAAA,iBAAA,UAAA,iBAEwC,mFhD6oLxC,4BgDjpLF,oBAKI,kBAAA,sBAAA,UAAA,uBhDmpLJ,2BgD/oLA,oBAEE,kBAAA,kBAAA,UAAA,kBAEwC,mFhDkpLxC,2BgDtpLF,oBAKI,kBAAA,uBAAA,UAAA,wBASJ,8BAEI,QAAA,EACA,oBAAA,IACA,oBAAA,QhDipLJ,sDACA,uDgDtpLA,qCAUI,QAAA,EAVJ,0ChD2pLA,2CgD5oLI,QAAA,EhDmpLJ,0CACA,0CgDnqLA,mChDgqLA,mCACA,qCgD1oLI,kBAAA,cAAA,UAAA,cAEwC,mFhDmpL1C,0CACA,0CgD7qLF,mChD0qLE,mCACA,qCgDjpLI,kBAAA,mBAAA,UAAA,oBhD0pLN,uBgDhpLA,uBAEE,SAAA,SACA,IAAA,EACA,OAAA,EAEA,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OACA,MAAA,IACA,MAAA,KACA,WAAA,OACA,QAAA,GhDspLF,6BADA,6BE/vLE,6BAAA,6B8CgHE,MAAA,KACA,gBAAA,KACA,QAAA,EACA,QAAA,GAGJ,uBACE,KAAA,EAKF,uBACE,MAAA,EhDmpLF,4BgD5oLA,4BAEE,QAAA,aACA,MAAA,KACA,OAAA,KACA,WAAA,YAAA,UAAA,OAAA,OACA,gBAAA,KAAA,KAEF,4BACE,iBAAA,+LAEF,4BACE,iBAAA,+LASF,qBACE,SAAA,SACA,MAAA,EACA,OAAA,KACA,KAAA,EACA,QAAA,GACA,QAAA,YAAA,QAAA,KACA,cAAA,OAAA,gBAAA,OACA,aAAA,EAEA,aAAA,IACA,YAAA,IACA,WAAA,KAZF,wBAeI,SAAA,SACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,OAAA,IACA,aAAA,IACA,YAAA,IACA,YAAA,OACA,OAAA,QACA,iBAAA,qBAvBJ,gCA2BM,SAAA,SACA,IAAA,MACA,KAAA,EACA,QAAA,aACA,MAAA,KACA,OAAA,KACA,QAAA,GAjCN,+BAoCM,SAAA,SACA,OAAA,MACA,KAAA,EACA,QAAA,aACA,MAAA,KACA,OAAA,KACA,QAAA,GA1CN,6BA+CI,iBAAA,KASJ,kBACE,SAAA,SACA,MAAA,IACA,OAAA,KACA,KAAA,IACA,QAAA,GACA,YAAA,KACA,eAAA,KACA,MAAA,KACA,WAAA,OCxOF,gBAAqB,eAAA,mBACrB,WAAqB,eAAA,cACrB,cAAqB,eAAA,iBACrB,cAAqB,eAAA,iBACrB,mBAAqB,eAAA,sBACrB,gBAAqB,eAAA,mBCFnB,YACE,iBAAA,kBhDUF,mBAAA,mBF+3LF,wBADA,wBkDn4LM,iBAAA,kBANJ,cACE,iBAAA,kBhDUF,qBAAA,qBFy4LF,0BADA,0BkD74LM,iBAAA,kBANJ,YACE,iBAAA,kBhDUF,mBAAA,mBFm5LF,wBADA,wBkDv5LM,iBAAA,kBANJ,SACE,iBAAA,kBhDUF,gBAAA,gBF65LF,qBADA,qBkDj6LM,iBAAA,kBANJ,YACE,iBAAA,kBhDUF,mBAAA,mBFu6LF,wBADA,wBkD36LM,iBAAA,kBANJ,WACE,iBAAA,kBhDUF,kBAAA,kBFi7LF,uBADA,uBkDr7LM,iBAAA,kBANJ,UACE,iBAAA,kBhDUF,iBAAA,iBF27LF,sBADA,sBkD/7LM,iBAAA,kBANJ,SACE,iBAAA,kBhDUF,gBAAA,gBFq8LF,qBADA,qBkDz8LM,iBAAA,kBCCN,UACE,iBAAA,eAGF,gBACE,iBAAA,sBCXF,QAAkB,OAAA,IAAA,MAAA,kBAClB,YAAkB,WAAA,IAAA,MAAA,kBAClB,cAAkB,aAAA,IAAA,MAAA,kBAClB,eAAkB,cAAA,IAAA,MAAA,kBAClB,aAAkB,YAAA,IAAA,MAAA,kBAElB,UAAmB,OAAA,YACnB,cAAmB,WAAA,YACnB,gBAAmB,aAAA,YACnB,iBAAmB,cAAA,YACnB,eAAmB,YAAA,YAGjB,gBACE,aAAA,kBADF,kBACE,aAAA,kBADF,gBACE,aAAA,kBADF,aACE,aAAA,kBADF,gBACE,aAAA,kBADF,eACE,aAAA,kBADF,cACE,aAAA,kBADF,aACE,aAAA,kBAIJ,cACE,aAAA,eAOF,SACE,cAAA,iBAEF,aACE,uBAAA,iBACA,wBAAA,iBAEF,eACE,wBAAA,iBACA,2BAAA,iBAEF,gBACE,2BAAA,iBACA,0BAAA,iBAEF,cACE,uBAAA,iBACA,0BAAA,iBAGF,gBACE,cAAA,cAGF,WACE,cAAA,YCxDA,iBACE,QAAA,MACA,MAAA,KACA,QAAA,GCMA,QAA2B,QAAA,eAC3B,UAA2B,QAAA,iBAC3B,gBAA2B,QAAA,uBAC3B,SAA2B,QAAA,gBAC3B,SAA2B,QAAA,gBAC3B,aAA2B,QAAA,oBAC3B,cAA2B,QAAA,qBAC3B,QAA2B,QAAA,sBAAA,QAAA,eAC3B,eAA2B,QAAA,6BAAA,QAAA,sB3C0C3B,yB2ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uB3C0C3B,yB2ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uB3C0C3B,yB2ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uB3C0C3B,0B2ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uBAS/B,aACE,cAAwB,QAAA,eACxB,gBAAwB,QAAA,iBACxB,sBAAwB,QAAA,uBACxB,eAAwB,QAAA,gBACxB,eAAwB,QAAA,gBACxB,mBAAwB,QAAA,oBACxB,oBAAwB,QAAA,qBACxB,cAAwB,QAAA,sBAAA,QAAA,eACxB,qBAAwB,QAAA,6BAAA,QAAA,uBClC1B,kBACE,SAAA,SACA,QAAA,MACA,MAAA,KACA,QAAA,EACA,SAAA,OALF,0BAQI,QAAA,MACA,QAAA,GATJ,yCvDsyMA,wBADA,yBAEA,yBACA,wBuDvxMI,SAAA,SACA,IAAA,EACA,OAAA,EACA,KAAA,EACA,MAAA,KACA,OAAA,KACA,OAAA,EAIJ,gCAEI,YAAA,WAIJ,gCAEI,YAAA,OAIJ,+BAEI,YAAA,IAIJ,+BAEI,YAAA,KCvCA,UAAgC,mBAAA,cAAA,eAAA,cAChC,aAAgC,mBAAA,iBAAA,eAAA,iBAChC,kBAAgC,mBAAA,sBAAA,eAAA,sBAChC,qBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,WAA8B,cAAA,eAAA,UAAA,eAC9B,aAA8B,cAAA,iBAAA,UAAA,iBAC9B,mBAA8B,cAAA,uBAAA,UAAA,uBAC9B,WAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,aAA8B,kBAAA,YAAA,UAAA,YAC9B,aAA8B,kBAAA,YAAA,UAAA,YAC9B,eAA8B,kBAAA,YAAA,YAAA,YAC9B,eAA8B,kBAAA,YAAA,YAAA,YAE9B,uBAAoC,cAAA,gBAAA,gBAAA,qBACpC,qBAAoC,cAAA,cAAA,gBAAA,mBACpC,wBAAoC,cAAA,iBAAA,gBAAA,iBACpC,yBAAoC,cAAA,kBAAA,gBAAA,wBACpC,wBAAoC,cAAA,qBAAA,gBAAA,uBAEpC,mBAAiC,eAAA,gBAAA,YAAA,qBACjC,iBAAiC,eAAA,cAAA,YAAA,mBACjC,oBAAiC,eAAA,iBAAA,YAAA,iBACjC,sBAAiC,eAAA,mBAAA,YAAA,mBACjC,qBAAiC,eAAA,kBAAA,YAAA,kBAEjC,qBAAkC,mBAAA,gBAAA,cAAA,qBAClC,mBAAkC,mBAAA,cAAA,cAAA,mBAClC,sBAAkC,mBAAA,iBAAA,cAAA,iBAClC,uBAAkC,mBAAA,kBAAA,cAAA,wBAClC,sBAAkC,mBAAA,qBAAA,cAAA,uBAClC,uBAAkC,mBAAA,kBAAA,cAAA,kBAElC,iBAAgC,oBAAA,eAAA,WAAA,eAChC,kBAAgC,oBAAA,gBAAA,WAAA,qBAChC,gBAAgC,oBAAA,cAAA,WAAA,mBAChC,mBAAgC,oBAAA,iBAAA,WAAA,iBAChC,qBAAgC,oBAAA,mBAAA,WAAA,mBAChC,oBAAgC,oBAAA,kBAAA,WAAA,kB7CYhC,yB6ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mB7CYhC,yB6ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mB7CYhC,yB6ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mB7CYhC,0B6ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mBC5ChC,YCDF,MAAA,eDEE,aCCF,MAAA,gBDAE,YCGF,MAAA,e/CmDE,yB8CxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gB/CmDE,yB8CxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gB/CmDE,yB8CxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gB/CmDE,0B8CxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gBCAA,iBAAyB,SAAA,iBAAzB,mBAAyB,SAAA,mBAAzB,mBAAyB,SAAA,mBAAzB,gBAAyB,SAAA,gBAAzB,iBAAyB,SAAA,yBAAA,SAAA,iBAK3B,WACE,SAAA,MACA,IAAA,EACA,MAAA,EACA,KAAA,EACA,QAAA,KAGF,cACE,SAAA,MACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,KAI4B,2DAD9B,YAEI,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,MC9BJ,SCEE,SAAA,SACA,MAAA,IACA,OAAA,IACA,QAAA,EACA,SAAA,OACA,KAAA,cACA,YAAA,OACA,OAAA,EAUA,0BAAA,yBAEE,SAAA,OACA,MAAA,KACA,OAAA,KACA,SAAA,QACA,KAAA,KACA,YAAA,OC5BJ,WAAa,WAAA,EAAA,QAAA,OAAA,2BACb,QAAU,WAAA,EAAA,MAAA,KAAA,0BACV,WAAa,WAAA,EAAA,KAAA,KAAA,2BACb,aAAe,WAAA,eCCX,MAAuB,MAAA,cAAvB,MAAuB,MAAA,cAAvB,MAAuB,MAAA,cAAvB,OAAuB,MAAA,eAAvB,QAAuB,MAAA,eAAvB,MAAuB,OAAA,cAAvB,MAAuB,OAAA,cAAvB,MAAuB,OAAA,cAAvB,OAAuB,OAAA,eAAvB,QAAuB,OAAA,eAI3B,QAAU,UAAA,eACV,QAAU,WAAA,eCAF,KAAgC,OAAA,YAChC,MhEitOR,MgE/sOU,WAAA,YAEF,MhEktOR,MgEhtOU,aAAA,YAEF,MhEmtOR,MgEjtOU,cAAA,YAEF,MhEotOR,MgEltOU,YAAA,YAfF,KAAgC,OAAA,iBAChC,MhEyuOR,MgEvuOU,WAAA,iBAEF,MhE0uOR,MgExuOU,aAAA,iBAEF,MhE2uOR,MgEzuOU,cAAA,iBAEF,MhE4uOR,MgE1uOU,YAAA,iBAfF,KAAgC,OAAA,gBAChC,MhEiwOR,MgE/vOU,WAAA,gBAEF,MhEkwOR,MgEhwOU,aAAA,gBAEF,MhEmwOR,MgEjwOU,cAAA,gBAEF,MhEowOR,MgElwOU,YAAA,gBAfF,KAAgC,OAAA,eAChC,MhEyxOR,MgEvxOU,WAAA,eAEF,MhE0xOR,MgExxOU,aAAA,eAEF,MhE2xOR,MgEzxOU,cAAA,eAEF,MhE4xOR,MgE1xOU,YAAA,eAfF,KAAgC,OAAA,iBAChC,MhEizOR,MgE/yOU,WAAA,iBAEF,MhEkzOR,MgEhzOU,aAAA,iBAEF,MhEmzOR,MgEjzOU,cAAA,iBAEF,MhEozOR,MgElzOU,YAAA,iBAfF,KAAgC,OAAA,eAChC,MhEy0OR,MgEv0OU,WAAA,eAEF,MhE00OR,MgEx0OU,aAAA,eAEF,MhE20OR,MgEz0OU,cAAA,eAEF,MhE40OR,MgE10OU,YAAA,eAfF,KAAgC,QAAA,YAChC,MhEi2OR,MgE/1OU,YAAA,YAEF,MhEk2OR,MgEh2OU,cAAA,YAEF,MhEm2OR,MgEj2OU,eAAA,YAEF,MhEo2OR,MgEl2OU,aAAA,YAfF,KAAgC,QAAA,iBAChC,MhEy3OR,MgEv3OU,YAAA,iBAEF,MhE03OR,MgEx3OU,cAAA,iBAEF,MhE23OR,MgEz3OU,eAAA,iBAEF,MhE43OR,MgE13OU,aAAA,iBAfF,KAAgC,QAAA,gBAChC,MhEi5OR,MgE/4OU,YAAA,gBAEF,MhEk5OR,MgEh5OU,cAAA,gBAEF,MhEm5OR,MgEj5OU,eAAA,gBAEF,MhEo5OR,MgEl5OU,aAAA,gBAfF,KAAgC,QAAA,eAChC,MhEy6OR,MgEv6OU,YAAA,eAEF,MhE06OR,MgEx6OU,cAAA,eAEF,MhE26OR,MgEz6OU,eAAA,eAEF,MhE46OR,MgE16OU,aAAA,eAfF,KAAgC,QAAA,iBAChC,MhEi8OR,MgE/7OU,YAAA,iBAEF,MhEk8OR,MgEh8OU,cAAA,iBAEF,MhEm8OR,MgEj8OU,eAAA,iBAEF,MhEo8OR,MgEl8OU,aAAA,iBAfF,KAAgC,QAAA,eAChC,MhEy9OR,MgEv9OU,YAAA,eAEF,MhE09OR,MgEx9OU,cAAA,eAEF,MhE29OR,MgEz9OU,eAAA,eAEF,MhE49OR,MgE19OU,aAAA,eAMN,QAAmB,OAAA,eACnB,ShE49OJ,SgE19OM,WAAA,eAEF,ShE69OJ,SgE39OM,aAAA,eAEF,ShE89OJ,SgE59OM,cAAA,eAEF,ShE+9OJ,SgE79OM,YAAA,erDaF,yBqDjDI,QAAgC,OAAA,YAChC,ShEygPN,SgEvgPQ,WAAA,YAEF,ShEygPN,SgEvgPQ,aAAA,YAEF,ShEygPN,SgEvgPQ,cAAA,YAEF,ShEygPN,SgEvgPQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,ShE4hPN,SgE1hPQ,WAAA,iBAEF,ShE4hPN,SgE1hPQ,aAAA,iBAEF,ShE4hPN,SgE1hPQ,cAAA,iBAEF,ShE4hPN,SgE1hPQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,ShE+iPN,SgE7iPQ,WAAA,gBAEF,ShE+iPN,SgE7iPQ,aAAA,gBAEF,ShE+iPN,SgE7iPQ,cAAA,gBAEF,ShE+iPN,SgE7iPQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,ShEkkPN,SgEhkPQ,WAAA,eAEF,ShEkkPN,SgEhkPQ,aAAA,eAEF,ShEkkPN,SgEhkPQ,cAAA,eAEF,ShEkkPN,SgEhkPQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,ShEqlPN,SgEnlPQ,WAAA,iBAEF,ShEqlPN,SgEnlPQ,aAAA,iBAEF,ShEqlPN,SgEnlPQ,cAAA,iBAEF,ShEqlPN,SgEnlPQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,ShEwmPN,SgEtmPQ,WAAA,eAEF,ShEwmPN,SgEtmPQ,aAAA,eAEF,ShEwmPN,SgEtmPQ,cAAA,eAEF,ShEwmPN,SgEtmPQ,YAAA,eAfF,QAAgC,QAAA,YAChC,ShE2nPN,SgEznPQ,YAAA,YAEF,ShE2nPN,SgEznPQ,cAAA,YAEF,ShE2nPN,SgEznPQ,eAAA,YAEF,ShE2nPN,SgEznPQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,ShE8oPN,SgE5oPQ,YAAA,iBAEF,ShE8oPN,SgE5oPQ,cAAA,iBAEF,ShE8oPN,SgE5oPQ,eAAA,iBAEF,ShE8oPN,SgE5oPQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,ShEiqPN,SgE/pPQ,YAAA,gBAEF,ShEiqPN,SgE/pPQ,cAAA,gBAEF,ShEiqPN,SgE/pPQ,eAAA,gBAEF,ShEiqPN,SgE/pPQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,ShEorPN,SgElrPQ,YAAA,eAEF,ShEorPN,SgElrPQ,cAAA,eAEF,ShEorPN,SgElrPQ,eAAA,eAEF,ShEorPN,SgElrPQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,ShEusPN,SgErsPQ,YAAA,iBAEF,ShEusPN,SgErsPQ,cAAA,iBAEF,ShEusPN,SgErsPQ,eAAA,iBAEF,ShEusPN,SgErsPQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,ShE0tPN,SgExtPQ,YAAA,eAEF,ShE0tPN,SgExtPQ,cAAA,eAEF,ShE0tPN,SgExtPQ,eAAA,eAEF,ShE0tPN,SgExtPQ,aAAA,eAMN,WAAmB,OAAA,eACnB,YhEwtPF,YgEttPI,WAAA,eAEF,YhEwtPF,YgEttPI,aAAA,eAEF,YhEwtPF,YgEttPI,cAAA,eAEF,YhEwtPF,YgEttPI,YAAA,gBrDaF,yBqDjDI,QAAgC,OAAA,YAChC,ShEmwPN,SgEjwPQ,WAAA,YAEF,ShEmwPN,SgEjwPQ,aAAA,YAEF,ShEmwPN,SgEjwPQ,cAAA,YAEF,ShEmwPN,SgEjwPQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,ShEsxPN,SgEpxPQ,WAAA,iBAEF,ShEsxPN,SgEpxPQ,aAAA,iBAEF,ShEsxPN,SgEpxPQ,cAAA,iBAEF,ShEsxPN,SgEpxPQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,ShEyyPN,SgEvyPQ,WAAA,gBAEF,ShEyyPN,SgEvyPQ,aAAA,gBAEF,ShEyyPN,SgEvyPQ,cAAA,gBAEF,ShEyyPN,SgEvyPQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,ShE4zPN,SgE1zPQ,WAAA,eAEF,ShE4zPN,SgE1zPQ,aAAA,eAEF,ShE4zPN,SgE1zPQ,cAAA,eAEF,ShE4zPN,SgE1zPQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,ShE+0PN,SgE70PQ,WAAA,iBAEF,ShE+0PN,SgE70PQ,aAAA,iBAEF,ShE+0PN,SgE70PQ,cAAA,iBAEF,ShE+0PN,SgE70PQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,ShEk2PN,SgEh2PQ,WAAA,eAEF,ShEk2PN,SgEh2PQ,aAAA,eAEF,ShEk2PN,SgEh2PQ,cAAA,eAEF,ShEk2PN,SgEh2PQ,YAAA,eAfF,QAAgC,QAAA,YAChC,ShEq3PN,SgEn3PQ,YAAA,YAEF,ShEq3PN,SgEn3PQ,cAAA,YAEF,ShEq3PN,SgEn3PQ,eAAA,YAEF,ShEq3PN,SgEn3PQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,ShEw4PN,SgEt4PQ,YAAA,iBAEF,ShEw4PN,SgEt4PQ,cAAA,iBAEF,ShEw4PN,SgEt4PQ,eAAA,iBAEF,ShEw4PN,SgEt4PQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,ShE25PN,SgEz5PQ,YAAA,gBAEF,ShE25PN,SgEz5PQ,cAAA,gBAEF,ShE25PN,SgEz5PQ,eAAA,gBAEF,ShE25PN,SgEz5PQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,ShE86PN,SgE56PQ,YAAA,eAEF,ShE86PN,SgE56PQ,cAAA,eAEF,ShE86PN,SgE56PQ,eAAA,eAEF,ShE86PN,SgE56PQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,ShEi8PN,SgE/7PQ,YAAA,iBAEF,ShEi8PN,SgE/7PQ,cAAA,iBAEF,ShEi8PN,SgE/7PQ,eAAA,iBAEF,ShEi8PN,SgE/7PQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,ShEo9PN,SgEl9PQ,YAAA,eAEF,ShEo9PN,SgEl9PQ,cAAA,eAEF,ShEo9PN,SgEl9PQ,eAAA,eAEF,ShEo9PN,SgEl9PQ,aAAA,eAMN,WAAmB,OAAA,eACnB,YhEk9PF,YgEh9PI,WAAA,eAEF,YhEk9PF,YgEh9PI,aAAA,eAEF,YhEk9PF,YgEh9PI,cAAA,eAEF,YhEk9PF,YgEh9PI,YAAA,gBrDaF,yBqDjDI,QAAgC,OAAA,YAChC,ShE6/PN,SgE3/PQ,WAAA,YAEF,ShE6/PN,SgE3/PQ,aAAA,YAEF,ShE6/PN,SgE3/PQ,cAAA,YAEF,ShE6/PN,SgE3/PQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,ShEghQN,SgE9gQQ,WAAA,iBAEF,ShEghQN,SgE9gQQ,aAAA,iBAEF,ShEghQN,SgE9gQQ,cAAA,iBAEF,ShEghQN,SgE9gQQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,ShEmiQN,SgEjiQQ,WAAA,gBAEF,ShEmiQN,SgEjiQQ,aAAA,gBAEF,ShEmiQN,SgEjiQQ,cAAA,gBAEF,ShEmiQN,SgEjiQQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,ShEsjQN,SgEpjQQ,WAAA,eAEF,ShEsjQN,SgEpjQQ,aAAA,eAEF,ShEsjQN,SgEpjQQ,cAAA,eAEF,ShEsjQN,SgEpjQQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,ShEykQN,SgEvkQQ,WAAA,iBAEF,ShEykQN,SgEvkQQ,aAAA,iBAEF,ShEykQN,SgEvkQQ,cAAA,iBAEF,ShEykQN,SgEvkQQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,ShE4lQN,SgE1lQQ,WAAA,eAEF,ShE4lQN,SgE1lQQ,aAAA,eAEF,ShE4lQN,SgE1lQQ,cAAA,eAEF,ShE4lQN,SgE1lQQ,YAAA,eAfF,QAAgC,QAAA,YAChC,ShE+mQN,SgE7mQQ,YAAA,YAEF,ShE+mQN,SgE7mQQ,cAAA,YAEF,ShE+mQN,SgE7mQQ,eAAA,YAEF,ShE+mQN,SgE7mQQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,ShEkoQN,SgEhoQQ,YAAA,iBAEF,ShEkoQN,SgEhoQQ,cAAA,iBAEF,ShEkoQN,SgEhoQQ,eAAA,iBAEF,ShEkoQN,SgEhoQQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,ShEqpQN,SgEnpQQ,YAAA,gBAEF,ShEqpQN,SgEnpQQ,cAAA,gBAEF,ShEqpQN,SgEnpQQ,eAAA,gBAEF,ShEqpQN,SgEnpQQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,ShEwqQN,SgEtqQQ,YAAA,eAEF,ShEwqQN,SgEtqQQ,cAAA,eAEF,ShEwqQN,SgEtqQQ,eAAA,eAEF,ShEwqQN,SgEtqQQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,ShE2rQN,SgEzrQQ,YAAA,iBAEF,ShE2rQN,SgEzrQQ,cAAA,iBAEF,ShE2rQN,SgEzrQQ,eAAA,iBAEF,ShE2rQN,SgEzrQQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,ShE8sQN,SgE5sQQ,YAAA,eAEF,ShE8sQN,SgE5sQQ,cAAA,eAEF,ShE8sQN,SgE5sQQ,eAAA,eAEF,ShE8sQN,SgE5sQQ,aAAA,eAMN,WAAmB,OAAA,eACnB,YhE4sQF,YgE1sQI,WAAA,eAEF,YhE4sQF,YgE1sQI,aAAA,eAEF,YhE4sQF,YgE1sQI,cAAA,eAEF,YhE4sQF,YgE1sQI,YAAA,gBrDaF,0BqDjDI,QAAgC,OAAA,YAChC,ShEuvQN,SgErvQQ,WAAA,YAEF,ShEuvQN,SgErvQQ,aAAA,YAEF,ShEuvQN,SgErvQQ,cAAA,YAEF,ShEuvQN,SgErvQQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,ShE0wQN,SgExwQQ,WAAA,iBAEF,ShE0wQN,SgExwQQ,aAAA,iBAEF,ShE0wQN,SgExwQQ,cAAA,iBAEF,ShE0wQN,SgExwQQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,ShE6xQN,SgE3xQQ,WAAA,gBAEF,ShE6xQN,SgE3xQQ,aAAA,gBAEF,ShE6xQN,SgE3xQQ,cAAA,gBAEF,ShE6xQN,SgE3xQQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,ShEgzQN,SgE9yQQ,WAAA,eAEF,ShEgzQN,SgE9yQQ,aAAA,eAEF,ShEgzQN,SgE9yQQ,cAAA,eAEF,ShEgzQN,SgE9yQQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,ShEm0QN,SgEj0QQ,WAAA,iBAEF,ShEm0QN,SgEj0QQ,aAAA,iBAEF,ShEm0QN,SgEj0QQ,cAAA,iBAEF,ShEm0QN,SgEj0QQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,ShEs1QN,SgEp1QQ,WAAA,eAEF,ShEs1QN,SgEp1QQ,aAAA,eAEF,ShEs1QN,SgEp1QQ,cAAA,eAEF,ShEs1QN,SgEp1QQ,YAAA,eAfF,QAAgC,QAAA,YAChC,ShEy2QN,SgEv2QQ,YAAA,YAEF,ShEy2QN,SgEv2QQ,cAAA,YAEF,ShEy2QN,SgEv2QQ,eAAA,YAEF,ShEy2QN,SgEv2QQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,ShE43QN,SgE13QQ,YAAA,iBAEF,ShE43QN,SgE13QQ,cAAA,iBAEF,ShE43QN,SgE13QQ,eAAA,iBAEF,ShE43QN,SgE13QQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,ShE+4QN,SgE74QQ,YAAA,gBAEF,ShE+4QN,SgE74QQ,cAAA,gBAEF,ShE+4QN,SgE74QQ,eAAA,gBAEF,ShE+4QN,SgE74QQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,ShEk6QN,SgEh6QQ,YAAA,eAEF,ShEk6QN,SgEh6QQ,cAAA,eAEF,ShEk6QN,SgEh6QQ,eAAA,eAEF,ShEk6QN,SgEh6QQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,ShEq7QN,SgEn7QQ,YAAA,iBAEF,ShEq7QN,SgEn7QQ,cAAA,iBAEF,ShEq7QN,SgEn7QQ,eAAA,iBAEF,ShEq7QN,SgEn7QQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,ShEw8QN,SgEt8QQ,YAAA,eAEF,ShEw8QN,SgEt8QQ,cAAA,eAEF,ShEw8QN,SgEt8QQ,eAAA,eAEF,ShEw8QN,SgEt8QQ,aAAA,eAMN,WAAmB,OAAA,eACnB,YhEs8QF,YgEp8QI,WAAA,eAEF,YhEs8QF,YgEp8QI,aAAA,eAEF,YhEs8QF,YgEp8QI,cAAA,eAEF,YhEs8QF,YgEp8QI,YAAA,gBCzCN,gBAAkB,YAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UAIlB,cAAiB,WAAA,kBACjB,aAAiB,YAAA,iBACjB,eCRE,SAAA,OACA,cAAA,SACA,YAAA,ODcE,WAAwB,WAAA,eACxB,YAAwB,WAAA,gBACxB,aAAwB,WAAA,iBtDsCxB,yBsDxCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBtDsCxB,yBsDxCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBtDsCxB,yBsDxCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBtDsCxB,0BsDxCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBAM5B,gBAAmB,eAAA,oBACnB,gBAAmB,eAAA,oBACnB,iBAAmB,eAAA,qBAInB,mBAAsB,YAAA,cACtB,oBAAsB,YAAA,cACtB,kBAAsB,YAAA,cACtB,aAAsB,WAAA,iBAItB,YAAc,MAAA,eEpCZ,cACE,MAAA,kBjEUF,qBAAA,qBiENI,MAAA,kBALJ,gBACE,MAAA,kBjEUF,uBAAA,uBiENI,MAAA,kBALJ,cACE,MAAA,kBjEUF,qBAAA,qBiENI,MAAA,kBALJ,WACE,MAAA,kBjEUF,kBAAA,kBiENI,MAAA,kBALJ,cACE,MAAA,kBjEUF,qBAAA,qBiENI,MAAA,kBALJ,aACE,MAAA,kBjEUF,oBAAA,oBiENI,MAAA,kBALJ,YACE,MAAA,kBjEUF,mBAAA,mBiENI,MAAA,kBALJ,WACE,MAAA,kBjEUF,kBAAA,kBiENI,MAAA,kBFqCN,WAAa,MAAA,kBACb,YAAc,MAAA,kBAEd,eAAiB,MAAA,yBACjB,eAAiB,MAAA,+BAIjB,WGpDE,KAAA,CAAA,CAAA,EAAA,EACA,MAAA,YACA,YAAA,KACA,iBAAA,YACA,OAAA,ECHF,SCCE,WAAA,kBDGF,WCHE,WAAA,iBCMA,axESF,ECqrRE,QADA,SuEvrRI,YAAA,eAEA,WAAA,eAGF,YAEI,gBAAA,UASJ,mBACE,QAAA,KAAA,YAAA,IxEgNN,IwEjMM,YAAA,mBvEsqRJ,WuEpqRE,IAEE,OAAA,IAAA,MAAA,QACA,kBAAA,MAQF,MACE,QAAA,mBvEgqRJ,IuE7pRE,GAEE,kBAAA,MvE+pRJ,GACA,GuE7pRE,EAGE,QAAA,EACA,OAAA,EAGF,GvE2pRF,GuEzpRI,iBAAA,MAQF,MACE,KAAA,GxEjCN,KwEoCM,UAAA,gB9DvFJ,W8D0FI,UAAA,gB1C9EN,Q0CmFM,QAAA,KrC/FN,OqCkGM,OAAA,IAAA,MAAA,K1DnGN,O0DuGM,gBAAA,mBADF,UvEqpRF,UuEhpRM,iBAAA,evEopRN,mBantRF,mB0DsEQ,OAAA,IAAA,MAAA,kB1DcR,Y0DTM,MAAA,QvEipRJ,wBAFA,euEhpRE,evEipRF,qBuE1oRM,aAAA,Q1DfR,sB0DoBM,MAAA,QACA,aAAA","sourcesContent":["/*!\n * Bootstrap v4.1.1 (https://getbootstrap.com/)\n * Copyright 2011-2018 The Bootstrap Authors\n * Copyright 2011-2018 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"code\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"input-group\";\n@import \"custom-forms\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"jumbotron\";\n@import \"alert\";\n@import \"progress\";\n@import \"media\";\n@import \"list-group\";\n@import \"close\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"utilities\";\n@import \"print\";\n",":root {\n // Custom variable values only support SassScript inside `#{}`.\n @each $color, $value in $colors {\n --#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$color}: #{$value};\n }\n\n @each $bp, $value in $grid-breakpoints {\n --breakpoint-#{$bp}: #{$value};\n }\n\n // Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --font-family-sans-serif: #{inspect($font-family-sans-serif)};\n --font-family-monospace: #{inspect($font-family-monospace)};\n}\n","// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Setting @viewport causes scrollbars to overlap content in IE11 and Edge, so\n// we force a non-overlapping, non-auto-hiding scrollbar to counteract.\n// 6. Change the default tap highlight to be completely transparent in iOS.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n}\n\nhtml {\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -ms-text-size-adjust: 100%; // 4\n -ms-overflow-style: scrollbar; // 5\n -webkit-tap-highlight-color: rgba($black, 0); // 6\n}\n\n// IE10+ doesn't honor `` in some cases.\n@at-root {\n @-ms-viewport {\n width: device-width;\n }\n}\n\n// stylelint-disable selector-list-comma-newline-after\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n// stylelint-enable selector-list-comma-newline-after\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Set an explicit initial text-align value so that we can later use the\n// the `inherit` value on things like `` elements.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n font-size: $font-size-base;\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: left; // 3\n background-color: $body-bg; // 2\n}\n\n// Suppress the focus outline on elements that cannot be accessed via keyboard.\n// This prevents an unwanted focus outline from appearing around elements that\n// might still respond to pointer events.\n//\n// Credit: https://github.com/suitcss/base\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n// stylelint-disable selector-list-comma-newline-after\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: $headings-margin-bottom;\n}\n// stylelint-enable selector-list-comma-newline-after\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n// Abbreviations\n//\n// 1. Remove the bottom border in Firefox 39-.\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Duplicate behavior to the data-* attribute for our tooltip plugin\n\nabbr[title],\nabbr[data-original-title] { // 4\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 1\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\ndfn {\n font-style: italic; // Add the correct font style in Android 4.3-\n}\n\n// stylelint-disable font-weight-notation\nb,\nstrong {\n font-weight: bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n// stylelint-enable font-weight-notation\n\nsmall {\n font-size: 80%; // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n -webkit-text-decoration-skip: objects; // Remove gaps in links underline in iOS 8+ and Safari 8+.\n\n @include hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href)\n// which have not been made explicitly keyboard-focusable (without tabindex).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n\n @include hover-focus {\n color: inherit;\n text-decoration: none;\n }\n\n &:focus {\n outline: 0;\n }\n}\n\n\n//\n// Code\n//\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-monospace;\n font-size: 1em; // Correct the odd `em` font sizing in all browsers.\n}\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n // We have @viewport set which causes scrollbars to overlap content in IE11 and Edge, so\n // we force a non-overlapping, non-auto-hiding scrollbar to counteract.\n -ms-overflow-style: scrollbar;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg:not(:root) {\n overflow: hidden; // Hide the overflow in IE\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $table-caption-color;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n // Matches default `` alignment by inheriting from the ``, or the\n // closest parent with a set `text-align`.\n text-align: inherit;\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: $label-margin-bottom;\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24093\nbutton {\n border-radius: 0;\n}\n\n// Work around a Firefox/IE bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n//\n// Credit: https://github.com/suitcss/base/\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`\n// controls in Android 4.\n// 2. Correct the inability to style clickable types in iOS and Safari.\nbutton,\nhtml [type=\"button\"], // 1\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button; // 2\n}\n\n// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box; // 1. Add the correct box sizing in IE 10-\n padding: 0; // 2. Remove the padding in IE 10-\n}\n\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n // Remove the default appearance of temporal inputs to avoid a Mobile Safari\n // bug where setting a custom line-height prevents text from being vertically\n // centered within the input.\n // See https://bugs.webkit.org/show_bug.cgi?id=139848\n // and https://github.com/twbs/bootstrap/issues/11266\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto; // Remove the default vertical scrollbar in IE.\n // Textareas should really only resize vertically so they don't break their (horizontal) containers.\n resize: vertical;\n}\n\nfieldset {\n // Browsers set a default `min-width: min-content;` on fieldsets,\n // unlike e.g. `

`s, which have `min-width: 0;` by default.\n // So we reset that to ensure fieldsets behave more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359\n // and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements\n min-width: 0;\n // Reset the default outline behavior of fieldsets so they don't affect page layout.\n padding: 0;\n margin: 0;\n border: 0;\n}\n\n// 1. Correct the text wrapping in Edge and IE.\n// 2. Correct the color inheritance from `fieldset` elements in IE.\nlegend {\n display: block;\n width: 100%;\n max-width: 100%; // 1\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit; // 2\n white-space: normal; // 1\n}\n\nprogress {\n vertical-align: baseline; // Add the correct vertical alignment in Chrome, Firefox, and Opera.\n}\n\n// Correct the cursor style of increment and decrement buttons in Chrome.\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n // This overrides the extra rounded corners on search inputs in iOS so that our\n // `.form-control` class can properly style them. Note that this cannot simply\n // be added to `.form-control` as it's not specific enough. For details, see\n // https://github.com/twbs/bootstrap/issues/11586.\n outline-offset: -2px; // 2. Correct the outline style in Safari.\n -webkit-appearance: none;\n}\n\n//\n// Remove the inner padding and cancel buttons in Chrome and Safari on macOS.\n//\n\n[type=\"search\"]::-webkit-search-cancel-button,\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// 1. Correct the inability to style clickable types in iOS and Safari.\n// 2. Change font properties to `inherit` in Safari.\n//\n\n::-webkit-file-upload-button {\n font: inherit; // 2\n -webkit-appearance: button; // 1\n}\n\n//\n// Correct element displays\n//\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item; // Add the correct display in all browsers\n cursor: pointer;\n}\n\ntemplate {\n display: none; // Add the correct display in IE\n}\n\n// Always hide an element with the `hidden` HTML attribute (from PureCSS).\n// Needed for proper display in IE 10-.\n[hidden] {\n display: none !important;\n}\n","/*!\n * Bootstrap v4.1.1 (https://getbootstrap.com/)\n * Copyright 2011-2018 The Bootstrap Authors\n * Copyright 2011-2018 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -ms-text-size-adjust: 100%;\n -ms-overflow-style: scrollbar;\n -webkit-tap-highlight-color: transparent;\n}\n\n@-ms-viewport {\n width: device-width;\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\ndfn {\n font-style: italic;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n -webkit-text-decoration-skip: objects;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus {\n outline: 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n -ms-overflow-style: scrollbar;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nbutton,\nhtml [type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-cancel-button,\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-family: inherit;\n font-weight: 500;\n line-height: 1.2;\n color: inherit;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014 \\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-break: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n.row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n min-height: 1px;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.col-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n}\n\n.col-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n -ms-flex-order: -1;\n order: -1;\n}\n\n.order-last {\n -ms-flex-order: 13;\n order: 13;\n}\n\n.order-0 {\n -ms-flex-order: 0;\n order: 0;\n}\n\n.order-1 {\n -ms-flex-order: 1;\n order: 1;\n}\n\n.order-2 {\n -ms-flex-order: 2;\n order: 2;\n}\n\n.order-3 {\n -ms-flex-order: 3;\n order: 3;\n}\n\n.order-4 {\n -ms-flex-order: 4;\n order: 4;\n}\n\n.order-5 {\n -ms-flex-order: 5;\n order: 5;\n}\n\n.order-6 {\n -ms-flex-order: 6;\n order: 6;\n}\n\n.order-7 {\n -ms-flex-order: 7;\n order: 7;\n}\n\n.order-8 {\n -ms-flex-order: 8;\n order: 8;\n}\n\n.order-9 {\n -ms-flex-order: 9;\n order: 9;\n}\n\n.order-10 {\n -ms-flex-order: 10;\n order: 10;\n}\n\n.order-11 {\n -ms-flex-order: 11;\n order: 11;\n}\n\n.order-12 {\n -ms-flex-order: 12;\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-sm-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-sm-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-sm-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-sm-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-sm-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-sm-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-sm-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-sm-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-sm-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-sm-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-sm-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-sm-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-sm-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-sm-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-sm-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-sm-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-md-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-md-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-md-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-md-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-md-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-md-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-md-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-md-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-md-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-md-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-md-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-md-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-md-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-md-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-md-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-md-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-lg-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-lg-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-lg-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-lg-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-lg-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-lg-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-lg-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-lg-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-lg-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-lg-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-lg-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-lg-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-lg-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-lg-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-lg-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-lg-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-xl-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-xl-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-xl-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-xl-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-xl-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-xl-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-xl-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-xl-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-xl-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-xl-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-xl-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-xl-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-xl-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-xl-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-xl-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-xl-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 1rem;\n background-color: transparent;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table .table {\n background-color: #fff;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #212529;\n border-color: #32383e;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #212529;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #32383e;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::-webkit-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-moz-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\nselect.form-control:not([size]):not([multiple]) {\n height: calc(2.25rem + 2px);\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n margin-bottom: 0;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control,\n.input-group-sm > .input-group-prepend > .form-control-plaintext.input-group-text,\n.input-group-sm > .input-group-append > .form-control-plaintext.input-group-text,\n.input-group-sm > .input-group-prepend > .form-control-plaintext.btn,\n.input-group-sm > .input-group-append > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control,\n.input-group-lg > .input-group-prepend > .form-control-plaintext.input-group-text,\n.input-group-lg > .input-group-append > .form-control-plaintext.input-group-text,\n.input-group-lg > .input-group-prepend > .form-control-plaintext.btn,\n.input-group-lg > .input-group-append > .form-control-plaintext.btn {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm, .input-group-sm > .form-control,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\nselect.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]),\n.input-group-sm > .input-group-prepend > select.input-group-text:not([size]):not([multiple]),\n.input-group-sm > .input-group-append > select.input-group-text:not([size]):not([multiple]),\n.input-group-sm > .input-group-prepend > select.btn:not([size]):not([multiple]),\n.input-group-sm > .input-group-append > select.btn:not([size]):not([multiple]) {\n height: calc(1.8125rem + 2px);\n}\n\n.form-control-lg, .input-group-lg > .form-control,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]),\n.input-group-lg > .input-group-prepend > select.input-group-text:not([size]):not([multiple]),\n.input-group-lg > .input-group-append > select.input-group-text:not([size]):not([multiple]),\n.input-group-lg > .input-group-prepend > select.btn:not([size]):not([multiple]),\n.input-group-lg > .input-group-append > select.btn:not([size]):not([multiple]) {\n height: calc(2.875rem + 2px);\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n -ms-flex-align: center;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: .5rem;\n margin-top: .1rem;\n font-size: .875rem;\n line-height: 1;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.8);\n border-radius: .2rem;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid, .was-validated\n.custom-select:valid,\n.custom-select.is-valid {\n border-color: #28a745;\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated\n.custom-select:valid:focus,\n.custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:valid ~ .valid-feedback,\n.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,\n.form-control.is-valid ~ .valid-tooltip, .was-validated\n.custom-select:valid ~ .valid-feedback,\n.was-validated\n.custom-select:valid ~ .valid-tooltip,\n.custom-select.is-valid ~ .valid-feedback,\n.custom-select.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:valid ~ .valid-feedback,\n.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback,\n.form-control-file.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n background-color: #71dd8a;\n}\n\n.was-validated .custom-control-input:valid ~ .valid-feedback,\n.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,\n.custom-control-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label::before, .custom-file-input.is-valid ~ .custom-file-label::before {\n border-color: inherit;\n}\n\n.was-validated .custom-file-input:valid ~ .valid-feedback,\n.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,\n.custom-file-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: .5rem;\n margin-top: .1rem;\n font-size: .875rem;\n line-height: 1;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.8);\n border-radius: .2rem;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated\n.custom-select:invalid,\n.custom-select.is-invalid {\n border-color: #dc3545;\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated\n.custom-select:invalid:focus,\n.custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control:invalid ~ .invalid-feedback,\n.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,\n.form-control.is-invalid ~ .invalid-tooltip, .was-validated\n.custom-select:invalid ~ .invalid-feedback,\n.was-validated\n.custom-select:invalid ~ .invalid-tooltip,\n.custom-select.is-invalid ~ .invalid-feedback,\n.custom-select.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:invalid ~ .invalid-feedback,\n.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback,\n.form-control-file.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n background-color: #efa2a9;\n}\n\n.was-validated .custom-control-input:invalid ~ .invalid-feedback,\n.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,\n.custom-control-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label::before, .custom-file-input.is-invalid ~ .custom-file-label::before {\n border-color: inherit;\n}\n\n.was-validated .custom-file-input:invalid ~ .invalid-feedback,\n.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,\n.custom-file-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover, .btn:focus {\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\n.btn:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.btn:not(:disabled):not(.disabled):active, .btn:not(:disabled):not(.disabled).active {\n background-image: none;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n background-color: transparent;\n background-image: none;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n background-color: transparent;\n background-image: none;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n background-color: transparent;\n background-image: none;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n background-color: transparent;\n background-image: none;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n background-color: transparent;\n background-image: none;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n background-color: transparent;\n background-image: none;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n background-color: transparent;\n background-image: none;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n background-color: transparent;\n background-image: none;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n background-color: transparent;\n border-color: transparent;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n border-color: transparent;\n box-shadow: none;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n width: 0;\n height: 0;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: -ms-inline-flexbox;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n -ms-flex: 0 1 auto;\n flex: 0 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group,\n.btn-group-vertical .btn + .btn,\n.btn-group-vertical .btn + .btn-group,\n.btn-group-vertical .btn-group + .btn,\n.btn-group-vertical .btn-group + .btn-group {\n margin-left: -1px;\n}\n\n.btn-toolbar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: center;\n justify-content: center;\n}\n\n.btn-group-vertical .btn,\n.btn-group-vertical .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: stretch;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file:focus {\n z-index: 3;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .custom-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: -ms-flexbox;\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n}\n\n.custom-control-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n}\n\n.custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n background-color: #dee2e6;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background-repeat: no-repeat;\n background-position: center center;\n background-size: 50% 50%;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before {\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::before {\n background-color: #007bff;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: #fff url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E\") no-repeat right 0.75rem center;\n background-size: 8px 10px;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 5px rgba(128, 189, 255, 0.5);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n opacity: 0;\n}\n\n.custom-select-sm {\n height: calc(1.8125rem + 2px);\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n font-size: 75%;\n}\n\n.custom-select-lg {\n height: calc(2.875rem + 2px);\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n font-size: 125%;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input:focus ~ .custom-file-label::after {\n border-color: #80bdff;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: 2.25rem;\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: 1px solid #ced4da;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n padding-left: 0;\n background-color: transparent;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: none;\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n -webkit-appearance: none;\n appearance: none;\n}\n\n.custom-range::-webkit-slider-thumb:focus {\n outline: none;\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-range::-moz-range-thumb:focus {\n outline: none;\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n appearance: none;\n}\n\n.custom-range::-ms-thumb:focus {\n outline: none;\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar > .container,\n.navbar > .container-fluid {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n -ms-flex-preferred-size: 100%;\n flex-basis: 100%;\n -ms-flex-positive: 1;\n flex-grow: 1;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-body {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img {\n width: 100%;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img-top {\n width: 100%;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img-bottom {\n width: 100%;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n -ms-flex-direction: column;\n flex-direction: column;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n }\n .card-group > .card {\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:first-child {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-top,\n .card-group > .card:first-child .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-bottom,\n .card-group > .card:first-child .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:last-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-top,\n .card-group > .card:last-child .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-bottom,\n .card-group > .card:last-child .card-footer {\n border-bottom-left-radius: 0;\n }\n .card-group > .card:only-child {\n border-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-top,\n .card-group > .card:only-child .card-header {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-bottom,\n .card-group > .card:only-child .card-footer {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) {\n border-radius: 0;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer {\n border-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n -webkit-column-count: 3;\n -moz-column-count: 3;\n column-count: 3;\n -webkit-column-gap: 1.25rem;\n -moz-column-gap: 1.25rem;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion .card:not(:first-of-type):not(:last-of-type) {\n border-bottom: 0;\n border-radius: 0;\n}\n\n.accordion .card:not(:first-of-type) .card-header:first-child {\n border-radius: 0;\n}\n\n.accordion .card:first-of-type {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion .card:last-of-type {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.breadcrumb {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: -ms-flexbox;\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 2;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-link:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 1;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\n.badge-primary[href]:hover, .badge-primary[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #0062cc;\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\n.badge-secondary[href]:hover, .badge-secondary[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #545b62;\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\n.badge-success[href]:hover, .badge-success[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #1e7e34;\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\n.badge-info[href]:hover, .badge-info[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #117a8b;\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\n.badge-warning[href]:hover, .badge-warning[href]:focus {\n color: #212529;\n text-decoration: none;\n background-color: #d39e00;\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\n.badge-danger[href]:hover, .badge-danger[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #bd2130;\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\n.badge-light[href]:hover, .badge-light[href]:focus {\n color: #212529;\n text-decoration: none;\n background-color: #dae0e5;\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.badge-dark[href]:hover, .badge-dark[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #1d2124;\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: -ms-flexbox;\n display: flex;\n height: 1rem;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: center;\n justify-content: center;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n -webkit-animation: progress-bar-stripes 1s linear infinite;\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n.media {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n}\n\n.media-body {\n -ms-flex: 1;\n flex: 1;\n}\n\n.list-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item:hover, .list-group-item:focus {\n z-index: 1;\n text-decoration: none;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-flush .list-group-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n\n.list-group-flush:first-child .list-group-item:first-child {\n border-top: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n border-bottom: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover, .close:focus {\n color: #000;\n text-decoration: none;\n opacity: .75;\n}\n\n.close:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n display: none;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: -webkit-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out;\n -webkit-transform: translate(0, -25%);\n transform: translate(0, -25%);\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n -webkit-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n\n.modal-dialog-centered {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n min-height: calc(100% - (0.5rem * 2));\n}\n\n.modal-content {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 1rem;\n border-bottom: 1px solid #e9ecef;\n border-top-left-radius: 0.3rem;\n border-top-right-radius: 0.3rem;\n}\n\n.modal-header .close {\n padding: 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: end;\n justify-content: flex-end;\n padding: 1rem;\n border-top: 1px solid #e9ecef;\n}\n\n.modal-footer > :not(:first-child) {\n margin-left: .25rem;\n}\n\n.modal-footer > :not(:last-child) {\n margin-right: .25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-centered {\n min-height: calc(100% - (1.75rem * 2));\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg {\n max-width: 800px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top .arrow, .bs-popover-auto[x-placement^=\"top\"] .arrow {\n bottom: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before,\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0;\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before {\n bottom: 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n bottom: 1px;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right .arrow, .bs-popover-auto[x-placement^=\"right\"] .arrow {\n left: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before,\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0.5rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before {\n left: 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n left: 1px;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom .arrow, .bs-popover-auto[x-placement^=\"bottom\"] .arrow {\n top: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before,\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n border-width: 0 0.5rem 0.5rem 0.5rem;\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before {\n top: 0;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n top: 1px;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left .arrow, .bs-popover-auto[x-placement^=\"left\"] .arrow {\n right: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before,\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n border-width: 0.5rem 0 0.5rem 0.5rem;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before {\n right: 0;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n right: 1px;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n color: inherit;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-item {\n position: relative;\n display: none;\n -ms-flex-align: center;\n align-items: center;\n width: 100%;\n transition: -webkit-transform 0.6s ease;\n transition: transform 0.6s ease;\n transition: transform 0.6s ease, -webkit-transform 0.6s ease;\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n perspective: 1000px;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next,\n.carousel-item-prev {\n position: absolute;\n top: 0;\n}\n\n.carousel-item-next.carousel-item-left,\n.carousel-item-prev.carousel-item-right {\n -webkit-transform: translateX(0);\n transform: translateX(0);\n}\n\n@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) {\n .carousel-item-next.carousel-item-left,\n .carousel-item-prev.carousel-item-right {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n }\n}\n\n.carousel-item-next,\n.active.carousel-item-right {\n -webkit-transform: translateX(100%);\n transform: translateX(100%);\n}\n\n@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) {\n .carousel-item-next,\n .active.carousel-item-right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n }\n}\n\n.carousel-item-prev,\n.active.carousel-item-left {\n -webkit-transform: translateX(-100%);\n transform: translateX(-100%);\n}\n\n@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) {\n .carousel-item-prev,\n .active.carousel-item-left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n }\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-duration: .6s;\n transition-property: opacity;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n opacity: 0;\n}\n\n.carousel-fade .carousel-item-next,\n.carousel-fade .carousel-item-prev,\n.carousel-fade .carousel-item.active,\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-prev {\n -webkit-transform: translateX(0);\n transform: translateX(0);\n}\n\n@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) {\n .carousel-fade .carousel-item-next,\n .carousel-fade .carousel-item-prev,\n .carousel-fade .carousel-item.active,\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-prev {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: .9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: transparent no-repeat center center;\n background-size: 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 10px;\n left: 0;\n z-index: 15;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n position: relative;\n -ms-flex: 0 1 auto;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: rgba(255, 255, 255, 0.5);\n}\n\n.carousel-indicators li::before {\n position: absolute;\n top: -10px;\n left: 0;\n display: inline-block;\n width: 100%;\n height: 10px;\n content: \"\";\n}\n\n.carousel-indicators li::after {\n position: absolute;\n bottom: -10px;\n left: 0;\n display: inline-block;\n width: 100%;\n height: 10px;\n content: \"\";\n}\n\n.carousel-indicators .active {\n background-color: #fff;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n}\n\n.d-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-md-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-print-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n}\n\n.flex-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n}\n\n.justify-content-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n}\n\n.align-items-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n}\n\n.align-items-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n}\n\n.align-items-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n}\n\n.align-items-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n}\n\n.align-content-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n}\n\n.align-content-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n}\n\n.align-content-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n}\n\n.align-content-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n}\n\n.align-content-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n}\n\n.align-self-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n}\n\n.align-self-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n}\n\n.align-self-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n}\n\n.align-self-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n}\n\n.align-self-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-sm-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-sm-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-sm-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-sm-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-sm-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-sm-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-sm-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-sm-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-md-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-md-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-md-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-md-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-md-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-md-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-md-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-md-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-md-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-md-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-md-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-md-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-md-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-md-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-md-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-md-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-lg-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-lg-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-lg-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-lg-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-lg-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-lg-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-lg-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-lg-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-xl-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-xl-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-xl-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-xl-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-xl-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-xl-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-xl-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-xl-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: -webkit-sticky !important;\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports ((position: -webkit-sticky) or (position: sticky)) {\n .sticky-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0062cc !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #545b62 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #1e7e34 !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #117a8b !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #d39e00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #bd2130 !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #dae0e5 !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #1d2124 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*!\n * Bootstrap v4.1.1 (https://getbootstrap.com/)\n * Copyright 2011-2018 The Bootstrap Authors\n * Copyright 2011-2018 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -ms-text-size-adjust: 100%;\n -ms-overflow-style: scrollbar;\n -webkit-tap-highlight-color: transparent;\n}\n\n@-ms-viewport {\n width: device-width;\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\ndfn {\n font-style: italic;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n -webkit-text-decoration-skip: objects;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus {\n outline: 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n -ms-overflow-style: scrollbar;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nbutton,\nhtml [type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-cancel-button,\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-family: inherit;\n font-weight: 500;\n line-height: 1.2;\n color: inherit;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014 \\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-break: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n.row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n min-height: 1px;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n}\n\n.col-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n order: -1;\n}\n\n.order-last {\n order: 13;\n}\n\n.order-0 {\n order: 0;\n}\n\n.order-1 {\n order: 1;\n}\n\n.order-2 {\n order: 2;\n}\n\n.order-3 {\n order: 3;\n}\n\n.order-4 {\n order: 4;\n}\n\n.order-5 {\n order: 5;\n}\n\n.order-6 {\n order: 6;\n}\n\n.order-7 {\n order: 7;\n}\n\n.order-8 {\n order: 8;\n}\n\n.order-9 {\n order: 9;\n}\n\n.order-10 {\n order: 10;\n}\n\n.order-11 {\n order: 11;\n}\n\n.order-12 {\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-sm-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-sm-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n order: -1;\n }\n .order-sm-last {\n order: 13;\n }\n .order-sm-0 {\n order: 0;\n }\n .order-sm-1 {\n order: 1;\n }\n .order-sm-2 {\n order: 2;\n }\n .order-sm-3 {\n order: 3;\n }\n .order-sm-4 {\n order: 4;\n }\n .order-sm-5 {\n order: 5;\n }\n .order-sm-6 {\n order: 6;\n }\n .order-sm-7 {\n order: 7;\n }\n .order-sm-8 {\n order: 8;\n }\n .order-sm-9 {\n order: 9;\n }\n .order-sm-10 {\n order: 10;\n }\n .order-sm-11 {\n order: 11;\n }\n .order-sm-12 {\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-md-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-md-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n order: -1;\n }\n .order-md-last {\n order: 13;\n }\n .order-md-0 {\n order: 0;\n }\n .order-md-1 {\n order: 1;\n }\n .order-md-2 {\n order: 2;\n }\n .order-md-3 {\n order: 3;\n }\n .order-md-4 {\n order: 4;\n }\n .order-md-5 {\n order: 5;\n }\n .order-md-6 {\n order: 6;\n }\n .order-md-7 {\n order: 7;\n }\n .order-md-8 {\n order: 8;\n }\n .order-md-9 {\n order: 9;\n }\n .order-md-10 {\n order: 10;\n }\n .order-md-11 {\n order: 11;\n }\n .order-md-12 {\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-lg-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-lg-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n order: -1;\n }\n .order-lg-last {\n order: 13;\n }\n .order-lg-0 {\n order: 0;\n }\n .order-lg-1 {\n order: 1;\n }\n .order-lg-2 {\n order: 2;\n }\n .order-lg-3 {\n order: 3;\n }\n .order-lg-4 {\n order: 4;\n }\n .order-lg-5 {\n order: 5;\n }\n .order-lg-6 {\n order: 6;\n }\n .order-lg-7 {\n order: 7;\n }\n .order-lg-8 {\n order: 8;\n }\n .order-lg-9 {\n order: 9;\n }\n .order-lg-10 {\n order: 10;\n }\n .order-lg-11 {\n order: 11;\n }\n .order-lg-12 {\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-xl-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none;\n }\n .col-xl-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n order: -1;\n }\n .order-xl-last {\n order: 13;\n }\n .order-xl-0 {\n order: 0;\n }\n .order-xl-1 {\n order: 1;\n }\n .order-xl-2 {\n order: 2;\n }\n .order-xl-3 {\n order: 3;\n }\n .order-xl-4 {\n order: 4;\n }\n .order-xl-5 {\n order: 5;\n }\n .order-xl-6 {\n order: 6;\n }\n .order-xl-7 {\n order: 7;\n }\n .order-xl-8 {\n order: 8;\n }\n .order-xl-9 {\n order: 9;\n }\n .order-xl-10 {\n order: 10;\n }\n .order-xl-11 {\n order: 11;\n }\n .order-xl-12 {\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 1rem;\n background-color: transparent;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table .table {\n background-color: #fff;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #212529;\n border-color: #32383e;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #212529;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #32383e;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\nselect.form-control:not([size]):not([multiple]) {\n height: calc(2.25rem + 2px);\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n margin-bottom: 0;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control,\n.input-group-sm > .input-group-prepend > .form-control-plaintext.input-group-text,\n.input-group-sm > .input-group-append > .form-control-plaintext.input-group-text,\n.input-group-sm > .input-group-prepend > .form-control-plaintext.btn,\n.input-group-sm > .input-group-append > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control,\n.input-group-lg > .input-group-prepend > .form-control-plaintext.input-group-text,\n.input-group-lg > .input-group-append > .form-control-plaintext.input-group-text,\n.input-group-lg > .input-group-prepend > .form-control-plaintext.btn,\n.input-group-lg > .input-group-append > .form-control-plaintext.btn {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm, .input-group-sm > .form-control,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\nselect.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]),\n.input-group-sm > .input-group-prepend > select.input-group-text:not([size]):not([multiple]),\n.input-group-sm > .input-group-append > select.input-group-text:not([size]):not([multiple]),\n.input-group-sm > .input-group-prepend > select.btn:not([size]):not([multiple]),\n.input-group-sm > .input-group-append > select.btn:not([size]):not([multiple]) {\n height: calc(1.8125rem + 2px);\n}\n\n.form-control-lg, .input-group-lg > .form-control,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]),\n.input-group-lg > .input-group-prepend > select.input-group-text:not([size]):not([multiple]),\n.input-group-lg > .input-group-append > select.input-group-text:not([size]):not([multiple]),\n.input-group-lg > .input-group-prepend > select.btn:not([size]):not([multiple]),\n.input-group-lg > .input-group-append > select.btn:not([size]):not([multiple]) {\n height: calc(2.875rem + 2px);\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: inline-flex;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: .5rem;\n margin-top: .1rem;\n font-size: .875rem;\n line-height: 1;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.8);\n border-radius: .2rem;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid, .was-validated\n.custom-select:valid,\n.custom-select.is-valid {\n border-color: #28a745;\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated\n.custom-select:valid:focus,\n.custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:valid ~ .valid-feedback,\n.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,\n.form-control.is-valid ~ .valid-tooltip, .was-validated\n.custom-select:valid ~ .valid-feedback,\n.was-validated\n.custom-select:valid ~ .valid-tooltip,\n.custom-select.is-valid ~ .valid-feedback,\n.custom-select.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:valid ~ .valid-feedback,\n.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback,\n.form-control-file.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n background-color: #71dd8a;\n}\n\n.was-validated .custom-control-input:valid ~ .valid-feedback,\n.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,\n.custom-control-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label::before, .custom-file-input.is-valid ~ .custom-file-label::before {\n border-color: inherit;\n}\n\n.was-validated .custom-file-input:valid ~ .valid-feedback,\n.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,\n.custom-file-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: .5rem;\n margin-top: .1rem;\n font-size: .875rem;\n line-height: 1;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.8);\n border-radius: .2rem;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated\n.custom-select:invalid,\n.custom-select.is-invalid {\n border-color: #dc3545;\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated\n.custom-select:invalid:focus,\n.custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control:invalid ~ .invalid-feedback,\n.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,\n.form-control.is-invalid ~ .invalid-tooltip, .was-validated\n.custom-select:invalid ~ .invalid-feedback,\n.was-validated\n.custom-select:invalid ~ .invalid-tooltip,\n.custom-select.is-invalid ~ .invalid-feedback,\n.custom-select.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:invalid ~ .invalid-feedback,\n.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback,\n.form-control-file.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n background-color: #efa2a9;\n}\n\n.was-validated .custom-control-input:invalid ~ .invalid-feedback,\n.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,\n.custom-control-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label::before, .custom-file-input.is-invalid ~ .custom-file-label::before {\n border-color: inherit;\n}\n\n.was-validated .custom-file-input:invalid ~ .invalid-feedback,\n.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,\n.custom-file-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: flex;\n flex-flow: row wrap;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: flex;\n flex: 0 0 auto;\n flex-flow: row wrap;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n align-items: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n user-select: none;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover, .btn:focus {\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\n.btn:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.btn:not(:disabled):not(.disabled):active, .btn:not(:disabled):not(.disabled).active {\n background-image: none;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n background-color: transparent;\n background-image: none;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n background-color: transparent;\n background-image: none;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n background-color: transparent;\n background-image: none;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n background-color: transparent;\n background-image: none;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n background-color: transparent;\n background-image: none;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n background-color: transparent;\n background-image: none;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n background-color: transparent;\n background-image: none;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n background-color: transparent;\n background-image: none;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n background-color: transparent;\n border-color: transparent;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n border-color: transparent;\n box-shadow: none;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n width: 0;\n height: 0;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n flex: 0 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group,\n.btn-group-vertical .btn + .btn,\n.btn-group-vertical .btn + .btn-group,\n.btn-group-vertical .btn-group + .btn,\n.btn-group-vertical .btn-group + .btn-group {\n margin-left: -1px;\n}\n\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.btn-group-vertical .btn,\n.btn-group-vertical .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file:focus {\n z-index: 3;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .custom-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: flex;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: flex;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n}\n\n.custom-control-inline {\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n}\n\n.custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n user-select: none;\n background-color: #dee2e6;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background-repeat: no-repeat;\n background-position: center center;\n background-size: 50% 50%;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before {\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::before {\n background-color: #007bff;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: #fff url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E\") no-repeat right 0.75rem center;\n background-size: 8px 10px;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 5px rgba(128, 189, 255, 0.5);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n opacity: 0;\n}\n\n.custom-select-sm {\n height: calc(1.8125rem + 2px);\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n font-size: 75%;\n}\n\n.custom-select-lg {\n height: calc(2.875rem + 2px);\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n font-size: 125%;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input:focus ~ .custom-file-label::after {\n border-color: #80bdff;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: 2.25rem;\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: 1px solid #ced4da;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n padding-left: 0;\n background-color: transparent;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: none;\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n appearance: none;\n}\n\n.custom-range::-webkit-slider-thumb:focus {\n outline: none;\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n appearance: none;\n}\n\n.custom-range::-moz-range-thumb:focus {\n outline: none;\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n appearance: none;\n}\n\n.custom-range::-ms-thumb:focus {\n outline: none;\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.nav {\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar > .container,\n.navbar > .container-fluid {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n flex-basis: 100%;\n flex-grow: 1;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n flex-flow: row nowrap;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-body {\n flex: 1 1 auto;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img {\n width: 100%;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img-top {\n width: 100%;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img-bottom {\n width: 100%;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck {\n display: flex;\n flex-direction: column;\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n display: flex;\n flex: 1 0 0%;\n flex-direction: column;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group {\n display: flex;\n flex-direction: column;\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n flex-flow: row wrap;\n }\n .card-group > .card {\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:first-child {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-top,\n .card-group > .card:first-child .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-bottom,\n .card-group > .card:first-child .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:last-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-top,\n .card-group > .card:last-child .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-bottom,\n .card-group > .card:last-child .card-footer {\n border-bottom-left-radius: 0;\n }\n .card-group > .card:only-child {\n border-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-top,\n .card-group > .card:only-child .card-header {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-bottom,\n .card-group > .card:only-child .card-footer {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) {\n border-radius: 0;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer {\n border-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n column-count: 3;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion .card:not(:first-of-type):not(:last-of-type) {\n border-bottom: 0;\n border-radius: 0;\n}\n\n.accordion .card:not(:first-of-type) .card-header:first-child {\n border-radius: 0;\n}\n\n.accordion .card:first-of-type {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion .card:last-of-type {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.breadcrumb {\n display: flex;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 2;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-link:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 1;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\n.badge-primary[href]:hover, .badge-primary[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #0062cc;\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\n.badge-secondary[href]:hover, .badge-secondary[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #545b62;\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\n.badge-success[href]:hover, .badge-success[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #1e7e34;\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\n.badge-info[href]:hover, .badge-info[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #117a8b;\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\n.badge-warning[href]:hover, .badge-warning[href]:focus {\n color: #212529;\n text-decoration: none;\n background-color: #d39e00;\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\n.badge-danger[href]:hover, .badge-danger[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #bd2130;\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\n.badge-light[href]:hover, .badge-light[href]:focus {\n color: #212529;\n text-decoration: none;\n background-color: #dae0e5;\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.badge-dark[href]:hover, .badge-dark[href]:focus {\n color: #fff;\n text-decoration: none;\n background-color: #1d2124;\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: flex;\n height: 1rem;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: flex;\n flex-direction: column;\n justify-content: center;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n.media {\n display: flex;\n align-items: flex-start;\n}\n\n.media-body {\n flex: 1;\n}\n\n.list-group {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item:hover, .list-group-item:focus {\n z-index: 1;\n text-decoration: none;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-flush .list-group-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n\n.list-group-flush:first-child .list-group-item:first-child {\n border-top: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n border-bottom: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover, .close:focus {\n color: #000;\n text-decoration: none;\n opacity: .75;\n}\n\n.close:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n display: none;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: transform 0.3s ease-out;\n transform: translate(0, -25%);\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n transform: translate(0, 0);\n}\n\n.modal-dialog-centered {\n display: flex;\n align-items: center;\n min-height: calc(100% - (0.5rem * 2));\n}\n\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 1rem;\n border-bottom: 1px solid #e9ecef;\n border-top-left-radius: 0.3rem;\n border-top-right-radius: 0.3rem;\n}\n\n.modal-header .close {\n padding: 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: 1rem;\n border-top: 1px solid #e9ecef;\n}\n\n.modal-footer > :not(:first-child) {\n margin-left: .25rem;\n}\n\n.modal-footer > :not(:last-child) {\n margin-right: .25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-centered {\n min-height: calc(100% - (1.75rem * 2));\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg {\n max-width: 800px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top .arrow, .bs-popover-auto[x-placement^=\"top\"] .arrow {\n bottom: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before,\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0;\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before {\n bottom: 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n bottom: 1px;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right .arrow, .bs-popover-auto[x-placement^=\"right\"] .arrow {\n left: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before,\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0.5rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before {\n left: 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n left: 1px;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom .arrow, .bs-popover-auto[x-placement^=\"bottom\"] .arrow {\n top: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before,\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n border-width: 0 0.5rem 0.5rem 0.5rem;\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before {\n top: 0;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n top: 1px;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left .arrow, .bs-popover-auto[x-placement^=\"left\"] .arrow {\n right: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before,\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n border-width: 0.5rem 0 0.5rem 0.5rem;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before {\n right: 0;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n right: 1px;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n color: inherit;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-item {\n position: relative;\n display: none;\n align-items: center;\n width: 100%;\n transition: transform 0.6s ease;\n backface-visibility: hidden;\n perspective: 1000px;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next,\n.carousel-item-prev {\n position: absolute;\n top: 0;\n}\n\n.carousel-item-next.carousel-item-left,\n.carousel-item-prev.carousel-item-right {\n transform: translateX(0);\n}\n\n@supports (transform-style: preserve-3d) {\n .carousel-item-next.carousel-item-left,\n .carousel-item-prev.carousel-item-right {\n transform: translate3d(0, 0, 0);\n }\n}\n\n.carousel-item-next,\n.active.carousel-item-right {\n transform: translateX(100%);\n}\n\n@supports (transform-style: preserve-3d) {\n .carousel-item-next,\n .active.carousel-item-right {\n transform: translate3d(100%, 0, 0);\n }\n}\n\n.carousel-item-prev,\n.active.carousel-item-left {\n transform: translateX(-100%);\n}\n\n@supports (transform-style: preserve-3d) {\n .carousel-item-prev,\n .active.carousel-item-left {\n transform: translate3d(-100%, 0, 0);\n }\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-duration: .6s;\n transition-property: opacity;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n opacity: 0;\n}\n\n.carousel-fade .carousel-item-next,\n.carousel-fade .carousel-item-prev,\n.carousel-fade .carousel-item.active,\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-prev {\n transform: translateX(0);\n}\n\n@supports (transform-style: preserve-3d) {\n .carousel-fade .carousel-item-next,\n .carousel-fade .carousel-item-prev,\n .carousel-fade .carousel-item.active,\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-prev {\n transform: translate3d(0, 0, 0);\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: .9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: transparent no-repeat center center;\n background-size: 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 10px;\n left: 0;\n z-index: 15;\n display: flex;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n position: relative;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: rgba(255, 255, 255, 0.5);\n}\n\n.carousel-indicators li::before {\n position: absolute;\n top: -10px;\n left: 0;\n display: inline-block;\n width: 100%;\n height: 10px;\n content: \"\";\n}\n\n.carousel-indicators li::after {\n position: absolute;\n bottom: -10px;\n left: 0;\n display: inline-block;\n width: 100%;\n height: 10px;\n content: \"\";\n}\n\n.carousel-indicators .active {\n background-color: #fff;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: flex !important;\n}\n\n.d-inline-flex {\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: flex !important;\n }\n .d-md-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: flex !important;\n }\n .d-print-inline-flex {\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n flex-direction: row !important;\n}\n\n.flex-column {\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n justify-content: center !important;\n}\n\n.justify-content-between {\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n justify-content: space-around !important;\n}\n\n.align-items-start {\n align-items: flex-start !important;\n}\n\n.align-items-end {\n align-items: flex-end !important;\n}\n\n.align-items-center {\n align-items: center !important;\n}\n\n.align-items-baseline {\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n align-items: stretch !important;\n}\n\n.align-content-start {\n align-content: flex-start !important;\n}\n\n.align-content-end {\n align-content: flex-end !important;\n}\n\n.align-content-center {\n align-content: center !important;\n}\n\n.align-content-between {\n align-content: space-between !important;\n}\n\n.align-content-around {\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n align-content: stretch !important;\n}\n\n.align-self-auto {\n align-self: auto !important;\n}\n\n.align-self-start {\n align-self: flex-start !important;\n}\n\n.align-self-end {\n align-self: flex-end !important;\n}\n\n.align-self-center {\n align-self: center !important;\n}\n\n.align-self-baseline {\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n flex-direction: row !important;\n }\n .flex-sm-column {\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n justify-content: center !important;\n }\n .justify-content-sm-between {\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n align-items: center !important;\n }\n .align-items-sm-baseline {\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n align-items: stretch !important;\n }\n .align-content-sm-start {\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n align-content: center !important;\n }\n .align-content-sm-between {\n align-content: space-between !important;\n }\n .align-content-sm-around {\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n align-self: auto !important;\n }\n .align-self-sm-start {\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n align-self: center !important;\n }\n .align-self-sm-baseline {\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n flex-direction: row !important;\n }\n .flex-md-column {\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n justify-content: center !important;\n }\n .justify-content-md-between {\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n justify-content: space-around !important;\n }\n .align-items-md-start {\n align-items: flex-start !important;\n }\n .align-items-md-end {\n align-items: flex-end !important;\n }\n .align-items-md-center {\n align-items: center !important;\n }\n .align-items-md-baseline {\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n align-items: stretch !important;\n }\n .align-content-md-start {\n align-content: flex-start !important;\n }\n .align-content-md-end {\n align-content: flex-end !important;\n }\n .align-content-md-center {\n align-content: center !important;\n }\n .align-content-md-between {\n align-content: space-between !important;\n }\n .align-content-md-around {\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n align-content: stretch !important;\n }\n .align-self-md-auto {\n align-self: auto !important;\n }\n .align-self-md-start {\n align-self: flex-start !important;\n }\n .align-self-md-end {\n align-self: flex-end !important;\n }\n .align-self-md-center {\n align-self: center !important;\n }\n .align-self-md-baseline {\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n flex-direction: row !important;\n }\n .flex-lg-column {\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n justify-content: center !important;\n }\n .justify-content-lg-between {\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n align-items: center !important;\n }\n .align-items-lg-baseline {\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n align-items: stretch !important;\n }\n .align-content-lg-start {\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n align-content: center !important;\n }\n .align-content-lg-between {\n align-content: space-between !important;\n }\n .align-content-lg-around {\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n align-self: auto !important;\n }\n .align-self-lg-start {\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n align-self: center !important;\n }\n .align-self-lg-baseline {\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n flex-direction: row !important;\n }\n .flex-xl-column {\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n justify-content: center !important;\n }\n .justify-content-xl-between {\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n align-items: center !important;\n }\n .align-items-xl-baseline {\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n align-items: stretch !important;\n }\n .align-content-xl-start {\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n align-content: center !important;\n }\n .align-content-xl-between {\n align-content: space-between !important;\n }\n .align-content-xl-around {\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n align-self: auto !important;\n }\n .align-self-xl-start {\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n align-self: center !important;\n }\n .align-self-xl-baseline {\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports (position: sticky) {\n .sticky-top {\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0062cc !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #545b62 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #1e7e34 !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #117a8b !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #d39e00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #bd2130 !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #dae0e5 !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #1d2124 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n\n/*# sourceMappingURL=bootstrap.css.map */","// Hover mixin and `$enable-hover-media-query` are deprecated.\n//\n// Origally added during our alphas and maintained during betas, this mixin was\n// designed to prevent `:hover` stickiness on iOS-an issue where hover styles\n// would persist after initial touch.\n//\n// For backward compatibility, we've kept these mixins and updated them to\n// always return their regular pseudo-classes instead of a shimmed media query.\n//\n// Issue: https://github.com/twbs/bootstrap/issues/25195\n\n@mixin hover {\n &:hover { @content; }\n}\n\n@mixin hover-focus {\n &:hover,\n &:focus {\n @content;\n }\n}\n\n@mixin plain-hover-focus {\n &,\n &:hover,\n &:focus {\n @content;\n }\n}\n\n@mixin hover-focus-active {\n &:hover,\n &:focus,\n &:active {\n @content;\n }\n}\n","// stylelint-disable declaration-no-important, selector-list-comma-newline-after\n\n//\n// Headings\n//\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1, .h1 { font-size: $h1-font-size; }\nh2, .h2 { font-size: $h2-font-size; }\nh3, .h3 { font-size: $h3-font-size; }\nh4, .h4 { font-size: $h4-font-size; }\nh5, .h5 { font-size: $h5-font-size; }\nh6, .h6 { font-size: $h6-font-size; }\n\n.lead {\n font-size: $lead-font-size;\n font-weight: $lead-font-weight;\n}\n\n// Type display classes\n.display-1 {\n font-size: $display1-size;\n font-weight: $display1-weight;\n line-height: $display-line-height;\n}\n.display-2 {\n font-size: $display2-size;\n font-weight: $display2-weight;\n line-height: $display-line-height;\n}\n.display-3 {\n font-size: $display3-size;\n font-weight: $display3-weight;\n line-height: $display-line-height;\n}\n.display-4 {\n font-size: $display4-size;\n font-weight: $display4-weight;\n line-height: $display-line-height;\n}\n\n\n//\n// Horizontal rules\n//\n\nhr {\n margin-top: $hr-margin-y;\n margin-bottom: $hr-margin-y;\n border: 0;\n border-top: $hr-border-width solid $hr-border-color;\n}\n\n\n//\n// Emphasis\n//\n\nsmall,\n.small {\n font-size: $small-font-size;\n font-weight: $font-weight-normal;\n}\n\nmark,\n.mark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n//\n// Lists\n//\n\n.list-unstyled {\n @include list-unstyled;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n @include list-unstyled;\n}\n.list-inline-item {\n display: inline-block;\n\n &:not(:last-child) {\n margin-right: $list-inline-padding;\n }\n}\n\n\n//\n// Misc\n//\n\n// Builds on `abbr`\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\n.blockquote {\n margin-bottom: $spacer;\n font-size: $blockquote-font-size;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%; // back to default font-size\n color: $blockquote-small-color;\n\n &::before {\n content: \"\\2014 \\00A0\"; // em dash, nbsp\n }\n}\n","// Lists\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n@mixin list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n","// Responsive images (ensure images don't scale beyond their parents)\n//\n// This is purposefully opt-in via an explicit class rather than being the default for all ``s.\n// We previously tried the \"images are responsive by default\" approach in Bootstrap v2,\n// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)\n// which weren't expecting the images within themselves to be involuntarily resized.\n// See also https://github.com/twbs/bootstrap/issues/18178\n.img-fluid {\n @include img-fluid;\n}\n\n\n// Image thumbnails\n.img-thumbnail {\n padding: $thumbnail-padding;\n background-color: $thumbnail-bg;\n border: $thumbnail-border-width solid $thumbnail-border-color;\n @include border-radius($thumbnail-border-radius);\n @include box-shadow($thumbnail-box-shadow);\n\n // Keep them at most 100% wide\n @include img-fluid;\n}\n\n//\n// Figures\n//\n\n.figure {\n // Ensures the caption's text aligns with the image.\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: ($spacer / 2);\n line-height: 1;\n}\n\n.figure-caption {\n font-size: $figure-caption-font-size;\n color: $figure-caption-color;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n@mixin img-fluid {\n // Part 1: Set a maximum relative to the parent\n max-width: 100%;\n // Part 2: Override the height to auto, otherwise images will be stretched\n // when setting a width and height attribute on the img element.\n height: auto;\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size.\n\n// stylelint-disable indentation, media-query-list-comma-newline-after\n@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) {\n background-image: url($file-1x);\n\n // Autoprefixer takes care of adding -webkit-min-device-pixel-ratio and -o-min-device-pixel-ratio,\n // but doesn't convert dppx=>dpi.\n // There's no such thing as unprefixed min-device-pixel-ratio since it's nonstandard.\n // Compatibility info: https://caniuse.com/#feat=css-media-resolution\n @media only screen and (min-resolution: 192dpi), // IE9-11 don't support dppx\n only screen and (min-resolution: 2dppx) { // Standardized\n background-image: url($file-2x);\n background-size: $width-1x $height-1x;\n }\n}\n","// Single side border-radius\n\n@mixin border-radius($radius: $border-radius) {\n @if $enable-rounded {\n border-radius: $radius;\n }\n}\n\n@mixin border-top-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-top-right-radius: $radius;\n }\n}\n\n@mixin border-right-radius($radius) {\n @if $enable-rounded {\n border-top-right-radius: $radius;\n border-bottom-right-radius: $radius;\n }\n}\n\n@mixin border-bottom-radius($radius) {\n @if $enable-rounded {\n border-bottom-right-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n\n@mixin border-left-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n","// Inline code\ncode {\n font-size: $code-font-size;\n color: $code-color;\n word-break: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n font-size: $kbd-font-size;\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n @include box-shadow($kbd-box-shadow);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: $nested-kbd-font-weight;\n @include box-shadow(none);\n }\n}\n\n// Blocks of code\npre {\n display: block;\n font-size: $code-font-size;\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: $pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n@if $enable-grid-classes {\n .container {\n @include make-container();\n @include make-container-max-widths();\n }\n}\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but with 100% width for\n// fluid, full width layouts.\n\n@if $enable-grid-classes {\n .container-fluid {\n @include make-container();\n }\n}\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n@if $enable-grid-classes {\n .row {\n @include make-row();\n }\n\n // Remove the negative margin from default .row, then the horizontal padding\n // from all immediate children columns (to prevent runaway style inheritance).\n .no-gutters {\n margin-right: 0;\n margin-left: 0;\n\n > .col,\n > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n }\n }\n}\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n@if $enable-grid-classes {\n @include make-grid-columns();\n}\n","/// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n@mixin make-container() {\n width: 100%;\n padding-right: ($grid-gutter-width / 2);\n padding-left: ($grid-gutter-width / 2);\n margin-right: auto;\n margin-left: auto;\n}\n\n\n// For each breakpoint, define the maximum width of the container in a media query\n@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) {\n @each $breakpoint, $container-max-width in $max-widths {\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n max-width: $container-max-width;\n }\n }\n}\n\n@mixin make-row() {\n display: flex;\n flex-wrap: wrap;\n margin-right: ($grid-gutter-width / -2);\n margin-left: ($grid-gutter-width / -2);\n}\n\n@mixin make-col-ready() {\n position: relative;\n // Prevent columns from becoming too narrow when at smaller grid tiers by\n // always setting `width: 100%;`. This works because we use `flex` values\n // later on to override this initial width.\n width: 100%;\n min-height: 1px; // Prevent collapsing\n padding-right: ($grid-gutter-width / 2);\n padding-left: ($grid-gutter-width / 2);\n}\n\n@mixin make-col($size, $columns: $grid-columns) {\n flex: 0 0 percentage($size / $columns);\n // Add a `max-width` to ensure content within each column does not blow out\n // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari\n // do not appear to require this.\n max-width: percentage($size / $columns);\n}\n\n@mixin make-col-offset($size, $columns: $grid-columns) {\n $num: $size / $columns;\n margin-left: if($num == 0, 0, percentage($num));\n}\n","// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n// >> breakpoint-next(sm)\n// md\n// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// md\n// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))\n// md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n $n: index($breakpoint-names, $name);\n @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n $min: map-get($breakpoints, $name);\n @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width. Null for the largest (last) breakpoint.\n// The maximum value is calculated as the minimum of the next one less 0.02px\n// to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths.\n// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max\n// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.\n// See https://bugs.webkit.org/show_bug.cgi?id=178261\n//\n// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 767.98px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n $next: breakpoint-next($name, $breakpoints);\n @return if($next, breakpoint-min($next, $breakpoints) - .02px, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash infront.\n// Useful for making responsive utilities.\n//\n// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"\" (Returns a blank string)\n// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n @if $min {\n @media (min-width: $min) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n $max: breakpoint-max($name, $breakpoints);\n @if $max {\n @media (max-width: $max) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($lower, $breakpoints);\n $max: breakpoint-max($upper, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($lower, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($upper, $breakpoints) {\n @content;\n }\n }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n $max: breakpoint-max($name, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($name, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($name, $breakpoints) {\n @content;\n }\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `$grid-columns`.\n\n@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {\n // Common properties for all breakpoints\n %grid-column {\n position: relative;\n width: 100%;\n min-height: 1px; // Prevent columns from collapsing when empty\n padding-right: ($gutter / 2);\n padding-left: ($gutter / 2);\n }\n\n @each $breakpoint in map-keys($breakpoints) {\n $infix: breakpoint-infix($breakpoint, $breakpoints);\n\n // Allow columns to stretch full width below their breakpoints\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @extend %grid-column;\n }\n }\n .col#{$infix},\n .col#{$infix}-auto {\n @extend %grid-column;\n }\n\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n // Provide basic `.col-{bp}` classes for equal-width flexbox columns\n .col#{$infix} {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col#{$infix}-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: none; // Reset earlier grid tiers\n }\n\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @include make-col($i, $columns);\n }\n }\n\n .order#{$infix}-first { order: -1; }\n\n .order#{$infix}-last { order: $columns + 1; }\n\n @for $i from 0 through $columns {\n .order#{$infix}-#{$i} { order: $i; }\n }\n\n // `$columns - 1` because offsetting by the width of an entire row isn't possible\n @for $i from 0 through ($columns - 1) {\n @if not ($infix == \"\" and $i == 0) { // Avoid emitting useless .offset-0\n .offset#{$infix}-#{$i} {\n @include make-col-offset($i, $columns);\n }\n }\n }\n }\n }\n}\n","//\n// Basic Bootstrap table\n//\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: $spacer;\n background-color: $table-bg; // Reset for nesting within parents with `background-color`.\n\n th,\n td {\n padding: $table-cell-padding;\n vertical-align: top;\n border-top: $table-border-width solid $table-border-color;\n }\n\n thead th {\n vertical-align: bottom;\n border-bottom: (2 * $table-border-width) solid $table-border-color;\n }\n\n tbody + tbody {\n border-top: (2 * $table-border-width) solid $table-border-color;\n }\n\n .table {\n background-color: $body-bg;\n }\n}\n\n\n//\n// Condensed table w/ half padding\n//\n\n.table-sm {\n th,\n td {\n padding: $table-cell-padding-sm;\n }\n}\n\n\n// Border versions\n//\n// Add or remove borders all around the table and between all the columns.\n\n.table-bordered {\n border: $table-border-width solid $table-border-color;\n\n th,\n td {\n border: $table-border-width solid $table-border-color;\n }\n\n thead {\n th,\n td {\n border-bottom-width: (2 * $table-border-width);\n }\n }\n}\n\n.table-borderless {\n th,\n td,\n thead th,\n tbody + tbody {\n border: 0;\n }\n}\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n tbody tr:nth-of-type(#{$table-striped-order}) {\n background-color: $table-accent-bg;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n tbody tr {\n @include hover {\n background-color: $table-hover-bg;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n@each $color, $value in $theme-colors {\n @include table-row-variant($color, theme-color-level($color, -9));\n}\n\n@include table-row-variant(active, $table-active-bg);\n\n\n// Dark styles\n//\n// Same table markup, but inverted color scheme: dark background and light text.\n\n// stylelint-disable-next-line no-duplicate-selectors\n.table {\n .thead-dark {\n th {\n color: $table-dark-color;\n background-color: $table-dark-bg;\n border-color: $table-dark-border-color;\n }\n }\n\n .thead-light {\n th {\n color: $table-head-color;\n background-color: $table-head-bg;\n border-color: $table-border-color;\n }\n }\n}\n\n.table-dark {\n color: $table-dark-color;\n background-color: $table-dark-bg;\n\n th,\n td,\n thead th {\n border-color: $table-dark-border-color;\n }\n\n &.table-bordered {\n border: 0;\n }\n\n &.table-striped {\n tbody tr:nth-of-type(odd) {\n background-color: $table-dark-accent-bg;\n }\n }\n\n &.table-hover {\n tbody tr {\n @include hover {\n background-color: $table-dark-hover-bg;\n }\n }\n }\n}\n\n\n// Responsive tables\n//\n// Generate series of `.table-responsive-*` classes for configuring the screen\n// size of where your table will overflow.\n\n.table-responsive {\n @each $breakpoint in map-keys($grid-breakpoints) {\n $next: breakpoint-next($breakpoint, $grid-breakpoints);\n $infix: breakpoint-infix($next, $grid-breakpoints);\n\n &#{$infix} {\n @include media-breakpoint-down($breakpoint) {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar; // See https://github.com/twbs/bootstrap/pull/10057\n\n // Prevent double border on horizontal scroll due to use of `display: block;`\n > .table-bordered {\n border: 0;\n }\n }\n }\n }\n}\n","// Tables\n\n@mixin table-row-variant($state, $background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table-#{$state} {\n &,\n > th,\n > td {\n background-color: $background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover {\n $hover-background: darken($background, 5%);\n\n .table-#{$state} {\n @include hover {\n background-color: $hover-background;\n\n > td,\n > th {\n background-color: $hover-background;\n }\n }\n }\n }\n}\n","// stylelint-disable selector-no-qualifying-type\n\n//\n// Textual form controls\n//\n\n.form-control {\n display: block;\n width: 100%;\n padding: $input-padding-y $input-padding-x;\n font-size: $font-size-base;\n line-height: $input-line-height;\n color: $input-color;\n background-color: $input-bg;\n background-clip: padding-box;\n border: $input-border-width solid $input-border-color;\n\n // Note: This has no effect on `s in CSS.\n @if $enable-rounded {\n // Manually use the if/else instead of the mixin to account for iOS override\n border-radius: $input-border-radius;\n } @else {\n // Otherwise undo the iOS default\n border-radius: 0;\n }\n\n @include box-shadow($input-box-shadow);\n @include transition($input-transition);\n\n // Unstyle the caret on ` receives focus\n // in IE and (under certain conditions) Edge, as it looks bad and cannot be made to\n // match the appearance of the native widget.\n // See https://github.com/twbs/bootstrap/issues/19398.\n color: $input-color;\n background-color: $input-bg;\n }\n}\n\n// Make file inputs better match text inputs by forcing them to new lines.\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n\n//\n// Labels\n//\n\n// For use with horizontal and inline forms, when you need the label (or legend)\n// text to align with the form controls.\n.col-form-label {\n padding-top: calc(#{$input-padding-y} + #{$input-border-width});\n padding-bottom: calc(#{$input-padding-y} + #{$input-border-width});\n margin-bottom: 0; // Override the `
+ + + + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/dashboard.html b/explorer/public/paper-dashboard/examples/dashboard.html new file mode 100644 index 0000000000..c2efeac9c8 --- /dev/null +++ b/explorer/public/paper-dashboard/examples/dashboard.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+
+
+
+ +
+
+
+
+

Capacity

+

150GB +

+

+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+

Revenue

+

$ 1,345 +

+

+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+

Errors

+

23 +

+

+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+

Followers

+

+45K +

+

+
+
+
+ +
+
+
+
+
+
+
+
Users Behavior
+

24 Hours performance

+
+
+ +
+ +
+
+
+
+
+
+
+
Email Statistics
+

Last Campaign Performance

+
+
+ +
+ +
+
+
+
+
+
NASDAQ: AAPL
+

Line Chart with Points

+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/icons.html b/explorer/public/paper-dashboard/examples/icons.html new file mode 100644 index 0000000000..cd6d0523d9 --- /dev/null +++ b/explorer/public/paper-dashboard/examples/icons.html @@ -0,0 +1,696 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+
100 Awesome Nucleo Icons
+

Handcrafted by our friends from + NucleoApp +

+
+
+
+
+
    +
  • + +

    nc-air-baloon

    +
  • +
  • + +

    nc-album-2

    +
  • +
  • + +

    nc-alert-circle-i

    +
  • +
  • + +

    nc-align-center

    +
  • +
  • + +

    nc-align-left-2

    +
  • +
  • + +

    nc-ambulance

    +
  • +
  • + +

    nc-app

    +
  • +
  • + +

    nc-atom

    +
  • +
  • + +

    nc-badge

    +
  • +
  • + +

    nc-bag-16

    +
  • +
  • + +

    nc-bank

    +
  • +
  • + +

    nc-basket

    +
  • +
  • + +

    nc-bell-55

    +
  • +
  • + +

    nc-bold

    +
  • +
  • + +

    nc-book-bookmark

    +
  • +
  • + +

    nc-bookmark-2

    +
  • +
  • + +

    nc-box-2

    +
  • +
  • + +

    nc-box

    +
  • +
  • + +

    nc-briefcase-24

    +
  • +
  • + +

    nc-bulb-63

    +
  • +
  • + +

    nc-bullet-list-67

    +
  • +
  • + +

    nc-bus-front-12

    +
  • +
  • + +

    nc-button-pause

    +
  • +
  • + +

    nc-button-play

    +
  • +
  • + +

    nc-button-power

    +
  • +
  • + +

    nc-calendar-60

    +
  • +
  • + +

    nc-camera-compact

    +
  • +
  • + +

    nc-caps-small

    +
  • +
  • + +

    nc-cart-simple

    +
  • +
  • + +

    nc-chart-bar-32

    +
  • +
  • + +

    nc-chart-pie-36

    +
  • +
  • + +

    nc-chat-33

    +
  • +
  • + +

    nc-check-2

    +
  • +
  • + +

    nc-circle-10

    +
  • +
  • + +

    nc-cloud-download-93

    +
  • +
  • + +

    nc-cloud-upload-94

    +
  • +
  • + +

    nc-compass-05

    +
  • +
  • + +

    nc-controller-modern

    +
  • +
  • + +

    nc-credit-card

    +
  • +
  • + +

    nc-delivery-fast

    +
  • +
  • + +

    nc-diamond

    +
  • +
  • + +

    nc-email-85

    +
  • +
  • + +

    nc-favourite-28

    +
  • +
  • + +

    nc-glasses-2

    +
  • +
  • + +

    nc-globe-2

    +
  • +
  • + +

    nc-globe

    +
  • +
  • + +

    nc-hat-3

    +
  • +
  • + +

    nc-headphones

    +
  • +
  • + +

    nc-html5

    +
  • +
  • + +

    nc-image

    +
  • +
  • + +

    nc-istanbul

    +
  • +
  • + +

    nc-key-25

    +
  • +
  • + +

    nc-laptop

    +
  • +
  • + +

    nc-layout-11

    +
  • +
  • + +

    nc-lock-circle-open

    +
  • +
  • + +

    nc-map-big

    +
  • +
  • + +

    nc-minimal-down

    +
  • +
  • + +

    nc-minimal-left

    +
  • +
  • + +

    nc-minimal-right

    +
  • +
  • + +

    nc-minimal-up

    +
  • +
  • + +

    nc-mobile

    +
  • +
  • + +

    nc-money-coins

    +
  • +
  • + +

    nc-note-03

    +
  • +
  • + +

    nc-palette

    +
  • +
  • + +

    nc-paper

    +
  • +
  • + +

    nc-pin-3

    +
  • +
  • + +

    nc-planet

    +
  • +
  • + +

    nc-refresh-69

    +
  • +
  • + +

    nc-ruler-pencil

    +
  • +
  • + +

    nc-satisfied

    +
  • +
  • + +

    nc-scissors

    +
  • +
  • + +

    nc-send

    +
  • +
  • + +

    nc-settings-gear-65

    +
  • +
  • + +

    nc-settings

    +
  • +
  • + +

    nc-share-66

    +
  • +
  • + +

    nc-shop

    +
  • +
  • + +

    nc-simple-add

    +
  • +
  • + +

    nc-simple-delete

    +
  • +
  • + +

    nc-simple-remove

    +
  • +
  • + +

    nc-single-02

    +
  • +
  • + +

    nc-single-copy-04

    +
  • +
  • + +

    nc-sound-wave

    +
  • +
  • + +

    nc-spaceship

    +
  • +
  • + +

    nc-sun-fog-29

    +
  • +
  • + +

    nc-support-17

    +
  • +
  • + +

    nc-tablet-2

    +
  • +
  • + +

    nc-tag-content

    +
  • +
  • + +

    nc-tap-01

    +
  • +
  • + +

    nc-tie-bow

    +
  • +
  • + +

    nc-tile-56

    +
  • +
  • + +

    nc-time-alarm

    +
  • +
  • + +

    nc-touch-id

    +
  • +
  • + +

    nc-trophy

    +
  • +
  • + +

    nc-tv-2

    +
  • +
  • + +

    nc-umbrella-13

    +
  • +
  • + +

    nc-user-run

    +
  • +
  • + +

    nc-vector

    +
  • +
  • + +

    nc-watch-time

    +
  • +
  • + +

    nc-world-2

    +
  • +
  • + +

    nc-zoom-split

    +
  • + +
+
+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/map.html b/explorer/public/paper-dashboard/examples/map.html new file mode 100644 index 0000000000..a9b220586e --- /dev/null +++ b/explorer/public/paper-dashboard/examples/map.html @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+ Google Maps +
+
+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/notifications.html b/explorer/public/paper-dashboard/examples/notifications.html new file mode 100644 index 0000000000..782f131a60 --- /dev/null +++ b/explorer/public/paper-dashboard/examples/notifications.html @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+
Notifications
+

Handcrafted by our friend + Robert McIntosh. Please checkout the + full documentation. +

+
+
+
+
+
+
+
Notifications Style
+
+
+
+ This is a plain notification +
+
+ + This is a notification with close button. +
+
+ + + This is a notification with close button and icon. +
+
+ + + This is a notification with close button and icon and have many lines. You can see that the icon and the close button are always vertically aligned. This is a beautiful notification. So you don't have to worry about the style. +
+
+
+
+
+
+
+
Notification states
+
+
+
+ + + Primary - This is a regular notification made with ".alert-primary" +
+
+ + + Info - This is a regular notification made with ".alert-info" +
+
+ + + Success - This is a regular notification made with ".alert-success" +
+
+ + + Warning - This is a regular notification made with ".alert-warning" +
+
+ + + Danger - This is a regular notification made with ".alert-danger" +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Notifications Places +

Click to view notifications

+

+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/tables.html b/explorer/public/paper-dashboard/examples/tables.html new file mode 100644 index 0000000000..5fa158d0e5 --- /dev/null +++ b/explorer/public/paper-dashboard/examples/tables.html @@ -0,0 +1,486 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+

Simple Table

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Country + + City + + Salary +
+ Dakota Rice + + Niger + + Oud-Turnhout + + $36,738 +
+ Minerva Hooper + + Curaçao + + Sinaai-Waas + + $23,789 +
+ Sage Rodriguez + + Netherlands + + Baileux + + $56,142 +
+ Philip Chaney + + Korea, South + + Overland Park + + $38,735 +
+ Doris Greene + + Malawi + + Feldkirchen in Kärnten + + $63,542 +
+ Mason Porter + + Chile + + Gloucester + + $78,615 +
+ Jon Porter + + Portugal + + Gloucester + + $98,615 +
+
+
+
+
+
+
+
+

Table on Plain Background

+

Here is a subtitle for this table

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Country + + City + + Salary +
+ Dakota Rice + + Niger + + Oud-Turnhout + + $36,738 +
+ Minerva Hooper + + Curaçao + + Sinaai-Waas + + $23,789 +
+ Sage Rodriguez + + Netherlands + + Baileux + + $56,142 +
+ Philip Chaney + + Korea, South + + Overland Park + + $38,735 +
+ Doris Greene + + Malawi + + Feldkirchen in Kärnten + + $63,542 +
+ Mason Porter + + Chile + + Gloucester + + $78,615 +
+ Jon Porter + + Portugal + + Gloucester + + $98,615 +
+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/typography.html b/explorer/public/paper-dashboard/examples/typography.html new file mode 100644 index 0000000000..7551beda64 --- /dev/null +++ b/explorer/public/paper-dashboard/examples/typography.html @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+
Paper Table Heading
+

Created using Montserrat Font Family

+
+
+
+

+ Header 1The Life of Paper Dashboard

+
+
+

+ Header 2The Life of Paper Dashboard

+
+
+

+ Header 3The Life of Paper Dashboard

+
+
+

+ Header 4The Life of Paper Dashboard

+
+
+
+ Header 5The Life of Paper Dashboard
+
+
+
+ Header 6The Life of Paper Dashboard
+
+
+

+ Paragraph + I will be the leader of a company that ends up being worth billions of dollars, because I got the answers. I understand culture. I am the nucleus. I think that’s a responsibility that I have, to push possibilities, to show people, this is the level that things could be at. +

+
+
+ Quote +
+

+ "I will be the leader of a company that ends up being worth billions of dollars, because I got the answers. I understand culture. I am the nucleus. I think that’s a responsibility that I have, to push possibilities, to show people, this is the level that things could be at." +
+
+ + - Noaa + +

+
+
+
+ Muted Text +

+ I will be the leader of a company that ends up being worth billions of dollars, because I got the answers... +

+
+
+ Primary Text +

+ I will be the leader of a company that ends up being worth billions of dollars, because I got the answers...

+
+
+ Info Text +

+ I will be the leader of a company that ends up being worth billions of dollars, because I got the answers...

+
+
+ Success Text +

+ I will be the leader of a company that ends up being worth billions of dollars, because I got the answers...

+
+
+ Warning Text +

+ I will be the leader of a company that ends up being worth billions of dollars, because I got the answers... +

+
+
+ Danger Text +

+ I will be the leader of a company that ends up being worth billions of dollars, because I got the answers...

+
+
+

+ Small Tag + Header with small subtitle +
+ Use "small" tag for the headers +

+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/upgrade.html b/explorer/public/paper-dashboard/examples/upgrade.html new file mode 100644 index 0000000000..301a87f554 --- /dev/null +++ b/explorer/public/paper-dashboard/examples/upgrade.html @@ -0,0 +1,301 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+

Paper Dashboard PRO

+

Are you looking for more components? Please check our Premium Version of Paper Dashboard PRO.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FreePRO
Components16160
Plugins413
Example Pages727
Login, Register, Pricing, Lock Pages
DataTables, VectorMap, SweetAlert, Wizard, jQueryValidation, FullCalendar etc...
Mini Sidebar
Premium Support
FreeJust $39
+ Current Version + + Upgrade to PRO +
+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/examples/user.html b/explorer/public/paper-dashboard/examples/user.html new file mode 100644 index 0000000000..3bb734be2e --- /dev/null +++ b/explorer/public/paper-dashboard/examples/user.html @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + Paper Dashboard 2 by Creative Tim + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
+ ... +
+
+
+ + ... +
Chet Faker
+
+

+ @chetfaker +

+
+

+ "I like the way you work it +
No diggity +
I wanna bag it up" +

+
+ +
+
+
+

Team Members

+
+
+
    +
  • +
    +
    +
    + Circle Image +
    +
    +
    + DJ Khaled +
    + + Offline + +
    +
    + +
    +
    +
  • +
  • +
    +
    +
    + Circle Image +
    +
    +
    + Creative Tim +
    + + Available + +
    +
    + +
    +
    +
  • +
  • +
    +
    +
    + Circle Image +
    +
    +
    + Flume +
    + + Busy + +
    +
    + +
    +
    +
  • +
+
+
+
+
+
+
+
Edit Profile
+
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ + © + , made with by Creative Tim + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + diff --git a/explorer/public/paper-dashboard/gulpfile.js b/explorer/public/paper-dashboard/gulpfile.js new file mode 100644 index 0000000000..96205b006f --- /dev/null +++ b/explorer/public/paper-dashboard/gulpfile.js @@ -0,0 +1,34 @@ +var gulp = require('gulp'); +var path = require('path'); +var sass = require('gulp-sass'); +var autoprefixer = require('gulp-autoprefixer'); +var sourcemaps = require('gulp-sourcemaps'); +var open = require('gulp-open'); + +var Paths = { + HERE: './', + DIST: 'dist/', + CSS: './assets/css/', + SCSS_TOOLKIT_SOURCES: './assets/scss/paper-dashboard.scss', + SCSS: './assets/scss/**/**' +}; + +gulp.task('compile-scss', function() { + return gulp.src(Paths.SCSS_TOOLKIT_SOURCES) + .pipe(sourcemaps.init()) + .pipe(sass().on('error', sass.logError)) + .pipe(autoprefixer()) + .pipe(sourcemaps.write(Paths.HERE)) + .pipe(gulp.dest(Paths.CSS)); +}); + +gulp.task('watch', function() { + gulp.watch(Paths.SCSS, ['compile-scss']); +}); + +gulp.task('open', function() { + gulp.src('examples/dashboard.html') + .pipe(open()); +}); + +gulp.task('open-app', ['open', 'watch']); \ No newline at end of file diff --git a/explorer/public/paper-dashboard/nucleo-icons.html b/explorer/public/paper-dashboard/nucleo-icons.html new file mode 100644 index 0000000000..53ab2fef46 --- /dev/null +++ b/explorer/public/paper-dashboard/nucleo-icons.html @@ -0,0 +1,1139 @@ + + + + + + + + + Nucleo Outline Web Font + + + + + +
+

Nucleo Outline Web Font v2.9

+

+ nucleoapp.com +

+
+ +
+
+

Arrows

+
    +
  • + +

    arrows-1_cloud-download-93

    +
  • +
  • + +

    arrows-1_cloud-upload-94

    +
  • +
  • + +

    arrows-1_minimal-down

    +
  • +
  • + +

    arrows-1_minimal-left

    +
  • +
  • + +

    arrows-1_minimal-right

    +
  • +
  • + +

    arrows-1_minimal-up

    +
  • +
  • + +

    arrows-1_refresh-69

    +
  • +
  • + +

    arrows-1_share-66

    +
  • +
+
+
+

Business & Finance

+
    +
  • + +

    business_badge

    +
  • +
  • + +

    business_bank

    +
  • +
  • + +

    business_briefcase-24

    +
  • +
  • + +

    business_bulb-63

    +
  • +
  • + +

    business_chart-bar-32

    +
  • +
  • + +

    business_chart-pie-36

    +
  • +
  • + +

    business_globe

    +
  • +
  • + +

    business_money-coins

    +
  • +
+
+
+

Clothes & Accessories

+
    +
  • + +

    clothes_tie-bow

    +
  • +
+
+
+

Design & Development

+
    +
  • + +

    design_app

    +
  • +
  • + +

    design_bullet-list-67

    +
  • +
  • + +

    design_image

    +
  • +
  • + +

    design_palette

    +
  • +
  • + +

    design_scissors

    +
  • +
  • + +

    design_vector

    +
  • +
  • + +

    design-2_html5

    +
  • +
  • + +

    design-2_ruler-pencil

    +
  • +
+
+
+

Emoticons

+
    +
  • + +

    emoticons_satisfied

    +
  • +
+
+
+

Files & Folders

+
    +
  • + +

    files_box

    +
  • +
  • + +

    files_paper

    +
  • +
  • + +

    files_single-copy-04

    +
  • +
+
+
+

Healthcare & Medical

+
    +
  • + +

    health_ambulance

    +
  • +
+
+
+

Loaders

+
    +
  • + +

    loader_gear

    +
  • +
  • + +

    loader_refresh

    +
  • +
+
+
+

Maps & Location

+
    +
  • + +

    location_bookmark

    +
  • +
  • + +

    location_compass-05

    +
  • +
  • + +

    location_map-big

    +
  • +
  • + +

    location_pin

    +
  • +
  • + +

    location_world

    +
  • +
+
+
+

Media

+
    +
  • + +

    media-1_album

    +
  • +
  • + +

    media-1_button-pause

    +
  • +
  • + +

    media-1_button-play

    +
  • +
  • + +

    media-1_button-power

    +
  • +
  • + +

    media-1_camera-compact

    +
  • +
  • + +

    media-2_note-03

    +
  • +
  • + +

    media-2_sound-wave

    +
  • +
+
+
+

Objects

+
    +
  • + +

    objects_diamond

    +
  • +
  • + +

    objects_globe

    +
  • +
  • + +

    objects_key-25

    +
  • +
  • + +

    objects_planet

    +
  • +
  • + +

    objects_spaceship

    +
  • +
  • + +

    objects_support-17

    +
  • +
  • + +

    objects_umbrella-13

    +
  • +
+
+
+

School & Education

+
    +
  • + +

    education_agenda-bookmark

    +
  • +
  • + +

    education_atom

    +
  • +
  • + +

    education_glasses

    +
  • +
  • + +

    education_hat

    +
  • +
  • + +

    education_paper

    +
  • +
+
+
+

Shopping

+
    +
  • + +

    shopping_bag-16

    +
  • +
  • + +

    shopping_basket

    +
  • +
  • + +

    shopping_box

    +
  • +
  • + +

    shopping_cart-simple

    +
  • +
  • + +

    shopping_credit-card

    +
  • +
  • + +

    shopping_delivery-fast

    +
  • +
  • + +

    shopping_shop

    +
  • +
  • + +

    shopping_tag-content

    +
  • +
+
+
+

Sport

+
    +
  • + +

    sport_trophy

    +
  • +
  • + +

    sport_user-run

    +
  • +
+
+
+

Technology

+
    +
  • + +

    tech_controller-modern

    +
  • +
  • + +

    tech_headphones

    +
  • +
  • + +

    tech_laptop

    +
  • +
  • + +

    tech_mobile

    +
  • +
  • + +

    tech_tablet

    +
  • +
  • + +

    tech_tv

    +
  • +
  • + +

    tech_watch-time

    +
  • +
+
+
+

Text Editing

+
    +
  • + +

    text_align-center

    +
  • +
  • + +

    text_align-left

    +
  • +
  • + +

    text_bold

    +
  • +
  • + +

    text_caps-small

    +
  • +
+
+
+

Touch Gestures

+
    +
  • + +

    gestures_tap-01

    +
  • +
+
+
+

Transportation

+
    +
  • + +

    transportation_air-baloon

    +
  • +
  • + +

    transportation_bus-front-12

    +
  • +
+
+
+

Travel

+
    +
  • + +

    travel_info

    +
  • +
  • + +

    travel_istanbul

    +
  • +
+
+
+

User Interface

+
    +
  • + +

    ui-1_bell-53

    +
  • +
  • + +

    ui-1_calendar-60

    +
  • +
  • + +

    ui-1_check

    +
  • +
  • + +

    ui-1_lock-circle-open

    +
  • +
  • + +

    ui-1_send

    +
  • +
  • + +

    ui-1_settings-gear-63

    +
  • +
  • + +

    ui-1_simple-add

    +
  • +
  • + +

    ui-1_simple-delete

    +
  • +
  • + +

    ui-1_simple-remove

    +
  • +
  • + +

    ui-1_zoom-bold

    +
  • +
  • + +

    ui-2_chat-round

    +
  • +
  • + +

    ui-2_favourite-28

    +
  • +
  • + +

    ui-2_like

    +
  • +
  • + +

    ui-2_settings-90

    +
  • +
  • + +

    ui-2_time-alarm

    +
  • +
  • + +

    ui-1_email-85

    +
  • +
+
+
+

Users

+
    +
  • + +

    users_circle-08

    +
  • +
  • + +

    users_single-02

    +
  • +
+
+
+ + + + \ No newline at end of file diff --git a/explorer/public/paper-dashboard/package.json b/explorer/public/paper-dashboard/package.json new file mode 100644 index 0000000000..7d83faf0ce --- /dev/null +++ b/explorer/public/paper-dashboard/package.json @@ -0,0 +1,17 @@ +{ + "name": "paper-dashboard-2", + "description": "Bootstrap 4 Paper Design", + "version": "2.0.0", + "homepage": "http://creative-tim.com/product/paper-dashboard-2", + "author": "Creative Tim", + "license": "Creative Tim License ", + "devDependencies": { + "gulp": "^3.9.1", + "gulp-autoprefixer": "^5.0.0", + "gulp-clean": "^0.4.0", + "gulp-install": "^1.1.0", + "gulp-sass": "^4.0.1", + "gulp-sourcemaps": "^2.6.4", + "gulp-open": "^3.0.1" + } +} diff --git a/explorer/src/jobs/mixmining.rs b/explorer/src/jobs/mixmining.rs new file mode 100644 index 0000000000..e55917c2c3 --- /dev/null +++ b/explorer/src/jobs/mixmining.rs @@ -0,0 +1,21 @@ +use crate::utils::file; +use reqwest::Error; + +const RELATIVE_PATH: &str = "api/mixmining/fullreport"; + +pub async fn renew_periodically(validator_base_url: &str) -> Result<(), Error> { + let url = format!("{}/{}", validator_base_url, RELATIVE_PATH); + + let topology_json = reqwest::get(&url).await?.text().await?; + + let save_path = std::env::current_exe() + .expect("Failed to evaluate current exe path") + .parent() + .expect("the binary itself has no parent path?!") + .join("public") + .join("downloads") + .join("mixmining.json"); + + file::save(topology_json, save_path); + Ok(()) +} diff --git a/explorer/src/jobs/mod.rs b/explorer/src/jobs/mod.rs new file mode 100644 index 0000000000..d0fbbd6e29 --- /dev/null +++ b/explorer/src/jobs/mod.rs @@ -0,0 +1,20 @@ +use log::*; +use tokio::time::{self, Duration}; + +mod mixmining; +mod topology; + +pub async fn start(validator_base_url: &str) { + let mut timer = time::interval(Duration::from_secs(10)); + loop { + timer.tick().await; + + if let Err(err) = topology::renew_periodically(validator_base_url).await { + warn!("Error refreshing topology: {}", err) + }; + + if let Err(err) = mixmining::renew_periodically(validator_base_url).await { + warn!("Error refreshing mixmining report: {}", err) + }; + } +} diff --git a/explorer/src/jobs/topology.rs b/explorer/src/jobs/topology.rs new file mode 100644 index 0000000000..5d1f175a4e --- /dev/null +++ b/explorer/src/jobs/topology.rs @@ -0,0 +1,21 @@ +use crate::utils::file; +use reqwest::Error; + +const RELATIVE_PATH: &str = "api/mixmining/topology"; + +pub async fn renew_periodically(validator_base_url: &str) -> Result<(), Error> { + let url = format!("{}/{}", validator_base_url, RELATIVE_PATH); + + let topology_json = reqwest::get(&url).await?.text().await?; + + let save_path = std::env::current_exe() + .expect("Failed to evaluate current exe path") + .parent() + .expect("the binary itself has no parent path?!") + .join("public") + .join("downloads") + .join("topology.json"); + + file::save(topology_json, save_path); + Ok(()) +} diff --git a/explorer/src/main.rs b/explorer/src/main.rs new file mode 100644 index 0000000000..1fdfb9e234 --- /dev/null +++ b/explorer/src/main.rs @@ -0,0 +1,79 @@ +#![feature(proc_macro_hygiene, decl_macro)] + +#[macro_use] +extern crate rocket; + +use clap::{App, Arg, ArgMatches}; +use rocket_contrib::serve::StaticFiles; +use tokio::sync::broadcast; + +mod jobs; +mod utils; +mod websockets; + +// this specifies number of messages that can be held by the channel, not number of the clients. +const BROADCAST_CAPACITY: usize = 10; +const VALIDATOR_ARG: &str = "validator"; +const METRICS_ARG: &str = "metrics"; + +fn parse_args<'a>() -> ArgMatches<'a> { + App::new("Nym Explorer") + .author("Nymtech") + .arg( + Arg::with_name(VALIDATOR_ARG) + .long(VALIDATOR_ARG) + .help("REST endpoint of the validator that explorer will use to periodically grab topology and node status.") + .takes_value(true) + ) + .arg( + Arg::with_name(METRICS_ARG) + .long(METRICS_ARG) + .help("websocket endpoint of the metrics server explorer will subscribe to and broadcast to its clients") + .takes_value(true) + ) + .get_matches() +} + +#[get("/")] +fn index() -> &'static str { + "Later we will chop this up into multiple routes, but for now we'll just use StaticFiles. Leaving this here as a pointer for the future." +} + +#[tokio::main] +async fn main() { + let matches = parse_args(); + let validator_base_url = matches + .value_of(VALIDATOR_ARG) + .unwrap_or_else(|| "http://testnet-validator1.nymtech.net:8081"); + let metrics_websocket_url = matches + .value_of(METRICS_ARG) + .unwrap_or_else(|| "wss://testnet-metrics.nymtech.net/ws") + .to_owned(); + + let public_path = std::env::current_exe() + .expect("Failed to evaluate current exe path") + .parent() + .expect("the binary itself has no parent path?!") + .join("public"); + + tokio::task::spawn_blocking(|| { + rocket::ignite() + .mount("/", StaticFiles::from(public_path)) + .launch() + }); + + let (sender, _) = broadcast::channel(BROADCAST_CAPACITY); + // the only reason for cloning the sender is that because more receivers can only be created + // out of senders + let sender_clone = sender.clone(); + + tokio::spawn(async move { + websockets::subscribe(&*metrics_websocket_url, sender).await; + }); + + tokio::spawn(async move { + websockets::listen(1648, sender_clone).await; + }); + + jobs::start(validator_base_url).await; +} diff --git a/explorer/src/utils/file.rs b/explorer/src/utils/file.rs new file mode 100644 index 0000000000..929880b293 --- /dev/null +++ b/explorer/src/utils/file.rs @@ -0,0 +1,16 @@ +use std::{fs::File, io::Write, path::Path}; + +pub fn save>(text: String, path: P) { + let path = path.as_ref(); + let display = path.display(); + + let mut file = match File::create(path) { + Err(why) => panic!("couldn't open {}: {}", display, why), + Ok(file) => file, + }; + + match file.write_all(text.as_bytes()) { + Err(why) => panic!("couldn't write to {}: {}", display, why), + Ok(_) => (), + } +} diff --git a/explorer/src/utils/mod.rs b/explorer/src/utils/mod.rs new file mode 100644 index 0000000000..1943c3c6d5 --- /dev/null +++ b/explorer/src/utils/mod.rs @@ -0,0 +1 @@ +pub(crate) mod file; diff --git a/explorer/src/websockets/client.rs b/explorer/src/websockets/client.rs new file mode 100644 index 0000000000..b258afc6bc --- /dev/null +++ b/explorer/src/websockets/client.rs @@ -0,0 +1,66 @@ +use log::*; +use tokio::net::TcpStream; +use tokio::stream::StreamExt; +use tokio::sync::broadcast; +use tokio_native_tls::TlsStream; +use tokio_tungstenite::tungstenite::{Error as WsError, Message}; +use tokio_tungstenite::WebSocketStream; +use tokio_tungstenite::{connect_async, stream::Stream}; + +pub(crate) type WsItem = Result; + +/// A websocket client which subscribes to the metrics centrally collected by the metrics server. +/// All metrics messages get copied out to this dashboard instance's clients. +pub(crate) struct MetricsWebsocketClient { + metrics_upstream: WebSocketStream>>, + broadcaster: broadcast::Sender, +} + +impl MetricsWebsocketClient { + /// Connect to the upstream metrics server + pub(crate) async fn connect( + metrics_address: &str, + broadcaster: broadcast::Sender, + ) -> Result { + let ws_stream = match connect_async(metrics_address).await { + Ok((ws_stream, _)) => ws_stream, + Err(e) => return Err(WebsocketError::NetworkError(e)), + }; + + info!("Subscribed to metrics websocket at {}", metrics_address); + + Ok(MetricsWebsocketClient { + metrics_upstream: ws_stream, + broadcaster, + }) + } + + /// When the metrics server sends a message, it should be copied out to the server and distributed + /// to all connected clients. + fn on_message(&self, item: WsItem) { + let ws_message = match item { + Ok(message) => message, + Err(err) => { + error!("failed to obtain valid websocket message - {}", err); + return; + } + }; + + match self.broadcaster.send(ws_message) { + Ok(received) => info!("broadcasted websocket metrics data to {} clients", received), + Err(_) => info!("no clients are currently subscribed"), + } + } + + pub(crate) async fn run(&mut self) { + while let Some(incoming) = self.metrics_upstream.next().await { + self.on_message(incoming) + } + info!("Our metrics server subscriber is finished!") + } +} + +#[derive(Debug)] +pub enum WebsocketError { + NetworkError(WsError), +} diff --git a/explorer/src/websockets/mod.rs b/explorer/src/websockets/mod.rs new file mode 100644 index 0000000000..4f633622db --- /dev/null +++ b/explorer/src/websockets/mod.rs @@ -0,0 +1,28 @@ +use client::MetricsWebsocketClient; +use log::*; +use server::DashboardWebsocketServer; +use tokio::sync::broadcast; +use tokio_tungstenite::tungstenite::Message; + +pub(crate) mod client; +mod server; + +pub async fn subscribe(metrics_socket: &str, sender: broadcast::Sender) { + let mut ws_client = match MetricsWebsocketClient::connect(metrics_socket, sender).await { + Ok(client) => client, + Err(e) => { + error!("metrics websocket failed to connect: {:?}", e); + std::process::exit(1) + } + }; + + ws_client.run().await; +} + +pub async fn listen(port: u16, sender: broadcast::Sender) { + let server = DashboardWebsocketServer::new(port, sender); + if let Err(err) = server.start().await { + error!("failed to start dashboard websocket server! - {:?}", err); + std::process::exit(1) + } +} diff --git a/explorer/src/websockets/server.rs b/explorer/src/websockets/server.rs new file mode 100644 index 0000000000..9b66c4545a --- /dev/null +++ b/explorer/src/websockets/server.rs @@ -0,0 +1,66 @@ +use futures_util::{SinkExt, StreamExt}; +use log::*; +use std::{io::Error as IoError, net::SocketAddr}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::broadcast; +use tokio_tungstenite::accept_async; +use tokio_tungstenite::tungstenite::Message; + +pub struct DashboardWebsocketServer { + sender: broadcast::Sender, + addr: String, +} + +impl DashboardWebsocketServer { + pub fn new(port: u16, sender: broadcast::Sender) -> DashboardWebsocketServer { + let addr = format!("[::]:{}", port); + DashboardWebsocketServer { addr, sender } + } + + pub async fn start(self) -> Result<(), IoError> { + let try_socket = TcpListener::bind(&self.addr).await; + + let mut listener = try_socket?; + info!("starting to listen on {}", self.addr); + while let Ok((stream, addr)) = listener.accept().await { + tokio::spawn(Self::handle_connection( + stream, + addr, + self.sender.subscribe(), + )); + } + + Ok(()) + } + + async fn handle_connection( + stream: TcpStream, + addr: SocketAddr, + mut receiver: broadcast::Receiver, + ) { + let mut ws_stream = match accept_async(stream).await { + Ok(ws_stream) => ws_stream, + Err(err) => { + warn!( + "error while performing the websocket handshake with {} - {:?}", + addr, err + ); + return; + } + }; + + info!("client connected from {}", addr); + while let Some(message) = receiver.next().await { + let message = message.expect("the websocket broadcaster is dead!"); + if let Err(err) = ws_stream.send(message).await { + warn!( + "failed to send subscribed message back to client ({}) - {}", + addr, err + ); + return; + } else { + info!("sent message to {}", addr) + } + } + } +} diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 6266205091..3de9f972f5 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -1,7 +1,6 @@ [package] -build = "build.rs" name = "nym-gateway" -version = "0.8.1" +version = "0.9.0" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] edition = "2018" @@ -10,32 +9,31 @@ edition = "2018" [dependencies] clap = "2.33.0" dirs = "2.0.2" +# using 4.0.0 release candidate as it's faster than 3.X and more importantly it resolves edge cases deadlocks +dashmap = "4.0.0-rc6" dotenv = "0.15.0" futures = "0.3" +humantime-serde = "1.0.1" log = "0.4" pretty_env_logger = "0.3" -rand = "0.7.2" +rand = "0.7" serde = { version = "1.0.104", features = ["derive"] } sled = "0.31" tokio = { version = "0.2", features = ["full"] } tokio-util = { version = "0.3.1", features = ["codec"] } -tokio-tungstenite = "0.11.0" +tokio-tungstenite = "0.11" # internal config = { path = "../common/config" } crypto = { path = "../common/crypto" } -directory-client = { path = "../common/client-libs/directory-client" } gateway-requests = { path = "gateway-requests" } mixnet-client = { path = "../common/client-libs/mixnet-client" } +mixnode-common = { path = "../common/mixnode-common" } nymsphinx = { path = "../common/nymsphinx" } pemstore = { path = "../common/pemstore" } +validator-client = { path = "../common/client-libs/validator-client" } +version-checker = { path = "../common/version-checker" } [dependencies.tungstenite] -version = "0.10.0" +version = "0.11" default-features = false - -[build-dependencies] -built = "0.4.3" - -[dev-dependencies] -tempfile = "3.1.0" \ No newline at end of file diff --git a/gateway/build.rs b/gateway/build.rs deleted file mode 100644 index 56d753472c..0000000000 --- a/gateway/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use built; - -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 31e78d4f2c..27f895fb3a 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -19,10 +19,11 @@ use crate::GatewayMacSize; use crypto::generic_array::typenum::Unsigned; use crypto::hmac::recompute_keyed_hmac_and_verify_tag; use crypto::symmetric::stream_cipher; -use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; +use nymsphinx::addressing::nodes::NymNodeRoutingAddressError; +use nymsphinx::forwarding::packet::{MixPacket, MixPacketFormattingError}; use nymsphinx::params::packet_sizes::PacketSize; use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; -use nymsphinx::{DestinationAddressBytes, SphinxPacket}; +use nymsphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; use std::{ convert::{TryFrom, TryInto}, @@ -73,10 +74,10 @@ pub enum GatewayRequestsError { RequestOfInvalidSize(usize), MalformedSphinxPacket, MalformedEncryption, + InvalidPacketMode, + InvalidMixPacket(MixPacketFormattingError), } -// to use it as `std::error::Error`, and we don't want to just derive is because we want -// the message to convey meanings of the usize tuple in RequestOfInvalidSize. impl fmt::Display for GatewayRequestsError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { use GatewayRequestsError::*; @@ -92,6 +93,8 @@ impl fmt::Display for GatewayRequestsError { ), MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"), MalformedEncryption => write!(f, "the received encrypted data was malformed"), + InvalidPacketMode => write!(f, "provided packet mode is invalid"), + InvalidMixPacket(err) => write!(f, "provided mix packet was malformed - {}", err) } } } @@ -102,6 +105,12 @@ impl From for GatewayRequestsError { } } +impl From for GatewayRequestsError { + fn from(err: MixPacketFormattingError) -> Self { + GatewayRequestsError::InvalidMixPacket(err) + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type", rename_all = "camelCase")] pub enum ClientControlRequest { @@ -172,10 +181,7 @@ impl ServerResponse { } pub fn is_error(&self) -> bool { - match self { - ServerResponse::Error { .. } => true, - _ => false, - } + matches!(self, ServerResponse::Error { .. }) } pub fn implies_successful_authentication(&self) -> bool { @@ -205,10 +211,7 @@ impl TryFrom for ServerResponse { } pub enum BinaryRequest { - ForwardSphinx { - address: NymNodeRoutingAddress, - sphinx_packet: SphinxPacket, - }, + ForwardSphinx(MixPacket), } // Right now the only valid `BinaryRequest` is a request to forward a sphinx packet. @@ -249,40 +252,15 @@ impl BinaryRequest { ); // right now there's only a single option possible which significantly simplifies the logic - // if we decided to allow for more 'binary' messages, the API wouldn't need to change - let address = NymNodeRoutingAddress::try_from_bytes(&message_bytes_mut)?; - let addr_offset = address.bytes_min_len(); - - let sphinx_packet_data = &message_bytes_mut[addr_offset..]; - let packet_size = sphinx_packet_data.len(); - if PacketSize::get_type(packet_size).is_err() { - // TODO: should this allow AckPacket sizes? - - Err(GatewayRequestsError::RequestOfInvalidSize(packet_size)) - } else { - let sphinx_packet = match SphinxPacket::from_bytes(sphinx_packet_data) { - Ok(packet) => packet, - Err(_) => return Err(GatewayRequestsError::MalformedSphinxPacket), - }; - - Ok(BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - }) - } + // if we decided to allow for more 'binary' messages, the API wouldn't need to change. + let mix_packet = MixPacket::try_from_bytes(message_bytes_mut)?; + Ok(BinaryRequest::ForwardSphinx(mix_packet)) } pub fn into_encrypted_tagged_bytes(self, shared_key: &SharedKeys) -> Vec { match self { - BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - } => { - let forwarding_data: Vec<_> = address - .as_bytes() - .into_iter() - .chain(sphinx_packet.to_bytes().into_iter()) - .collect(); + BinaryRequest::ForwardSphinx(mix_packet) => { + let forwarding_data = mix_packet.into_bytes(); // TODO: it could be theoretically slightly more efficient if the data wasn't taken // by reference because then it makes a copy for encryption rather than do it in place @@ -292,14 +270,8 @@ impl BinaryRequest { } // TODO: this will be encrypted, etc. - pub fn new_forward_request( - address: NymNodeRoutingAddress, - sphinx_packet: SphinxPacket, - ) -> BinaryRequest { - BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - } + pub fn new_forward_request(mix_packet: MixPacket) -> BinaryRequest { + BinaryRequest::ForwardSphinx(mix_packet) } pub fn into_ws_message(self, shared_key: &SharedKeys) -> Message { diff --git a/gateway/src/built_info.rs b/gateway/src/built_info.rs deleted file mode 100644 index 38b46d59e6..0000000000 --- a/gateway/src/built_info.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index 7e469cc116..e6ca156fe1 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -14,6 +14,7 @@ use crate::commands::override_config; use crate::config::persistence::pathfinder::GatewayPathfinder; +use crate::config::Config; use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::asymmetric::{encryption, identity}; @@ -97,51 +98,59 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true) ) .arg( - Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the node is sending presence data to") + Arg::with_name("validator") + .long("validator") + .help("REST endpoint of the validator the node is registering presence with") + .takes_value(true), + ) + .arg( + Arg::with_name("incentives-address") + .long("incentives-address") + .help("Optional, if participating in the incentives program, payment address") .takes_value(true), ) -} - -fn show_incentives_url() { - println!("\n##### NOTE #####"); - println!( - "\nIf you would like to join our testnet incentives program, please visit https://nymtech.net/incentives" - ); - println!("\n\n"); } pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); println!("Initialising gateway {}...", id); - let mut config = crate::config::Config::new(id); + let already_init = if Config::default_config_file_path(id).exists() { + println!("Gateway \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id); + true + } else { + false + }; + + let mut config = Config::new(id); config = override_config(config, matches); - let identity_keys = identity::KeyPair::new(); - let sphinx_keys = encryption::KeyPair::new(); - let pathfinder = GatewayPathfinder::new_from_config(&config); - pemstore::store_keypair( - &sphinx_keys, - &pemstore::KeyPairPath::new( - pathfinder.private_encryption_key().to_owned(), - pathfinder.public_encryption_key().to_owned(), - ), - ) - .expect("Failed to save sphinx keys"); + // if gateway was already initialised, don't generate new keys + if !already_init { + let identity_keys = identity::KeyPair::new(); + let sphinx_keys = encryption::KeyPair::new(); + let pathfinder = GatewayPathfinder::new_from_config(&config); + pemstore::store_keypair( + &sphinx_keys, + &pemstore::KeyPairPath::new( + pathfinder.private_encryption_key().to_owned(), + pathfinder.public_encryption_key().to_owned(), + ), + ) + .expect("Failed to save sphinx keys"); - pemstore::store_keypair( - &identity_keys, - &pemstore::KeyPairPath::new( - pathfinder.private_identity_key().to_owned(), - pathfinder.public_identity_key().to_owned(), - ), - ) - .expect("Failed to save identity keys"); + pemstore::store_keypair( + &identity_keys, + &pemstore::KeyPairPath::new( + pathfinder.private_identity_key().to_owned(), + pathfinder.public_identity_key().to_owned(), + ), + ) + .expect("Failed to save identity keys"); - println!("Saved identity and mixnet sphinx keypairs"); + println!("Saved identity and mixnet sphinx keypairs"); + } let config_save_location = config.get_config_file_save_location(); config @@ -150,6 +159,4 @@ pub fn execute(matches: &ArgMatches) { println!("Saved configuration file to {:?}", config_save_location); println!("Gateway configuration completed.\n\n\n"); - - show_incentives_url(); } diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 4a8c3eeff8..88cf63087e 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -15,8 +15,9 @@ use crate::config::Config; use clap::ArgMatches; -pub mod init; -pub mod run; +pub(crate) mod init; +pub(crate) mod run; +pub(crate) mod upgrade; pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { let mut was_mix_host_overridden = false; @@ -85,8 +86,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_clients_announce_port(clients_announce_port.unwrap()); } - if let Some(directory) = matches.value_of("directory") { - config = config.with_custom_directory(directory); + if let Some(validator) = matches.value_of("validator") { + config = config.with_custom_validator(validator); } if let Some(inboxes_dir) = matches.value_of("inboxes") { @@ -101,5 +102,9 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_location(location); } + if let Some(incentives_address) = matches.value_of("incentives-address") { + config = config.with_incentives_address(incentives_address); + } + config } diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 5f8aa5dd5a..9e7a73ff10 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -19,6 +19,8 @@ use crate::node::Gateway; use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::asymmetric::{encryption, identity}; +use log::*; +use version_checker::is_minor_version_compatible; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("run") @@ -104,9 +106,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true) ) .arg( - Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the gateway is sending presence data to") + Arg::with_name("validator") + .long("validator") + .help("REST endpoint of the validator the node is registering presence with") .takes_value(true), ) } @@ -152,17 +154,45 @@ fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair { identity_keypair } +// this only checks compatibility between config the binary. It does not take into consideration +// network version. It might do so in the future. +fn version_check(cfg: &Config) -> bool { + let binary_version = env!("CARGO_PKG_VERSION"); + let config_version = cfg.get_version(); + if binary_version != config_version { + warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version); + if is_minor_version_compatible(binary_version, config_version) { + info!("but they are still semver compatible. However, consider running the `upgrade` command"); + true + } else { + error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); + false + } + } else { + true + } +} + pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); println!("Starting gateway {}...", id); - let mut config = - Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id)) - .expect("Failed to load config file"); + let mut config = match Config::load_from_file(id) { + Ok(cfg) => cfg, + Err(err) => { + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err); + return; + } + }; config = override_config(config, matches); + if !version_check(&config) { + error!("failed the local version check"); + return; + } + let pathfinder = GatewayPathfinder::new_from_config(&config); let sphinx_keypair = load_sphinx_keys(&pathfinder); let identity = load_identity_keys(&pathfinder); @@ -178,8 +208,8 @@ pub fn execute(matches: &ArgMatches) { } println!( - "Directory server [presence]: {}", - config.get_presence_directory_server() + "Validator REST endpoint: {}", + config.get_validator_rest_endpoint() ); println!( diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs new file mode 100644 index 0000000000..9b5b11bf60 --- /dev/null +++ b/gateway/src/commands/upgrade.rs @@ -0,0 +1,192 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::config::DEFAULT_VALIDATOR_REST_ENDPOINT; +use crate::config::{Config, MISSING_VALUE}; +use clap::{App, Arg, ArgMatches}; +use config::NymConfig; +use std::fmt::Display; +use std::process; +use version_checker::{parse_version, Version}; + +fn print_start_upgrade(from: D1, to: D2) { + println!( + "\n==================\nTrying to upgrade gateway from {} to {} ...", + from, to + ); +} + +fn print_failed_upgrade(from: D1, to: D2) { + eprintln!( + "Upgrade from {} to {} failed!\n==================\n", + from, to + ); +} + +fn print_successful_upgrade(from: D1, to: D2) { + println!( + "Upgrade from {} to {} was successful!\n==================\n", + from, to + ); +} + +fn pre_090_upgrade(from: &str, config: Config, matches: &ArgMatches) -> Config { + // this is not extracted to separate function as you only have to manually pass version + // if upgrading from pre090 version + let from = match from.strip_prefix("v") { + Some(stripped) => stripped, + None => from, + }; + + let from = match from.strip_prefix("V") { + Some(stripped) => stripped, + None => from, + }; + + let from_version = parse_version(from).expect("invalid version provided!"); + if from_version.major == 0 && from_version.minor < 8 { + // technically this could be implemented, but is there any point in that? + eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead"); + process::exit(1) + } + + if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { + eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); + process::exit(1) + } + + // note: current is guaranteed to not have any `build` information suffix (nor pre-release + // information), as this was asserted at the beginning of this command) + // + // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate + // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) + // this way we don't need to have all the crazy paths on how to upgrade from any version to any + // other version. We just upgrade one minor version at a time. + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + let to_version = if current.major == 0 && current.minor == 9 { + current + } else { + Version::new(0, 9, 0) + }; + + print_start_upgrade(&from_version, &to_version); + + if config.get_validator_rest_endpoint() != MISSING_VALUE { + eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + } + + println!( + "Setting validator REST endpoint to to {}", + DEFAULT_VALIDATOR_REST_ENDPOINT + ); + + let mut upgraded_config = config + .with_custom_version(to_version.to_string().as_ref()) + .with_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + + if let Some(incentives_address) = matches.value_of("incentives address") { + upgraded_config = upgraded_config.with_incentives_address(incentives_address); + println!("Setting incentives address to {}", incentives_address); + } + + upgraded_config.save_to_file(None).unwrap_or_else(|err| { + eprintln!("failed to overwrite config file! - {:?}", err); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + }); + + print_successful_upgrade(from_version, to_version); + + upgraded_config +} + +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the gateway") + .arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-gateway we want to upgrade") + .takes_value(true) + .required(true), + ) + // the rest of arguments depend on the upgrade path + .arg(Arg::with_name("current version") + .long("current-version") + .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") + .takes_value(true) + ) + .arg(Arg::with_name("incentives address") + .long("incentives-address") + .help("Optional, if participating in the incentives program, payment address") + .takes_value(true) + ) +} + +pub fn execute(matches: &ArgMatches) { + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + + // technically this is not a correct way of checking it as a released version might contain valid build identifiers + // however, we are not using them ourselves at the moment and hence it should be fine. + // if we change our mind, we could easily tweak this code + if current.is_prerelease() || !current.build.is_empty() { + eprintln!( + "Trying to upgrade to a non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + let id = matches.value_of("id").unwrap(); + + let mut existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {:?}", err); + process::exit(1) + }); + + // versions fields were added in 0.9.0 + if existing_config.get_version() == MISSING_VALUE { + let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { + eprintln!( + "trying to upgrade from pre v0.9.0 without providing current system version!" + ); + process::exit(1) + }); + + // upgrades up to 0.9.0 + existing_config = pre_090_upgrade(self_reported_version, existing_config, &matches); + } + + let config_version = Version::parse(existing_config.get_version()).unwrap_or_else(|err| { + eprintln!("failed to parse node version! - {:?}", err); + process::exit(1) + }); + + if config_version.is_prerelease() || !config_version.build.is_empty() { + eprintln!( + "Trying to upgrade to from non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + // here be upgrade path to 0.10.0 and beyond based on version number from config + if config_version == current { + println!("You're using the most recent version!"); + } else { + eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current); + process::exit(1) + } +} diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index adab45a214..8f652ea8c5 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -15,26 +15,34 @@ use crate::config::template::config_template; use config::NymConfig; use log::*; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{self, IntoDeserializer, Visitor}, + Deserialize, Deserializer, Serialize, +}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; -use std::time; +use std::time::Duration; pub mod persistence; mod template; +pub(crate) const MISSING_VALUE: &str = "MISSING VALUE"; + // 'GATEWAY' const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; -const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net"; +pub(crate) const DEFAULT_VALIDATOR_REST_ENDPOINT: &str = + "http://testnet-validator1.nymtech.net:8081"; // 'DEBUG' // where applicable, the below are defined in milliseconds -const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 10_000; // 10s -const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s -const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min -const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s +const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); +const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_CACHE_ENTRY_TTL: Duration = Duration::from_millis(30_000); +const DEFAULT_MAXIMUM_RECONNECTION_ATTEMPTS: u32 = 20; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5; @@ -59,10 +67,6 @@ impl NymConfig for Config { config_template() } - fn config_file_name() -> String { - "config.toml".to_string() - } - fn default_root_directory() -> PathBuf { dirs::home_dir() .expect("Failed to evaluate $HOME value") @@ -89,6 +93,69 @@ impl NymConfig for Config { } } +// custom function is defined to deserialize based on whether field contains a pre 0.9.0 +// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 +// +// TODO: when we get to refactoring down the line, this code can just be removed +// and all Duration fields could just have #[serde(with = "humantime_serde")] instead +// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, +// for argument sake, 0.11.0 out +fn deserialize_duration<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct DurationVisitor; + + impl<'de> Visitor<'de> for DurationVisitor { + type Value = Duration; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("u64 or a duration") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + self.visit_u64(value as u64) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(Duration::from_millis(Deserialize::deserialize( + value.into_deserializer(), + )?)) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + humantime_serde::deserialize(value.into_deserializer()) + } + } + + deserializer.deserialize_any(DurationVisitor) +} + +fn deserialize_option_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + if s.is_empty() { + Ok(None) + } else { + Ok(Some(s)) + } +} + +pub fn missing_string_value() -> String { + MISSING_VALUE.to_string() +} + impl Config { pub fn new>(id: S) -> Self { Config::default().with_id(id) @@ -135,8 +202,8 @@ impl Config { self } - pub fn with_custom_directory>(mut self, directory_server: S) -> Self { - self.gateway.presence_directory_server = directory_server.into(); + pub fn with_custom_validator>(mut self, validator: S) -> Self { + self.gateway.validator_rest_url = validator.into(); self } @@ -321,6 +388,16 @@ impl Config { self } + pub fn with_custom_version(mut self, version: &str) -> Self { + self.gateway.version = version.to_string(); + self + } + + pub fn with_incentives_address>(mut self, incentives_address: S) -> Self { + self.gateway.incentives_address = Some(incentives_address.into()); + self + } + // getters pub fn get_config_file_save_location(&self) -> PathBuf { self.config_directory().join(Self::config_file_name()) @@ -346,12 +423,8 @@ impl Config { self.gateway.public_sphinx_key_file.clone() } - pub fn get_presence_directory_server(&self) -> String { - self.gateway.presence_directory_server.clone() - } - - pub fn get_presence_sending_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.presence_sending_delay) + pub fn get_validator_rest_endpoint(&self) -> String { + self.gateway.validator_rest_url.clone() } pub fn get_mix_listening_address(&self) -> SocketAddr { @@ -378,16 +451,20 @@ impl Config { self.clients_endpoint.ledger_path.clone() } - pub fn get_packet_forwarding_initial_backoff(&self) -> time::Duration { - time::Duration::from_millis(self.debug.packet_forwarding_initial_backoff) + pub fn get_packet_forwarding_initial_backoff(&self) -> Duration { + self.debug.packet_forwarding_initial_backoff } - pub fn get_packet_forwarding_maximum_backoff(&self) -> time::Duration { - time::Duration::from_millis(self.debug.packet_forwarding_maximum_backoff) + pub fn get_packet_forwarding_maximum_backoff(&self) -> Duration { + self.debug.packet_forwarding_maximum_backoff } - pub fn get_initial_connection_timeout(&self) -> time::Duration { - time::Duration::from_millis(self.debug.initial_connection_timeout) + pub fn get_initial_connection_timeout(&self) -> Duration { + self.debug.initial_connection_timeout + } + + pub fn get_packet_forwarding_max_reconnections(&self) -> u32 { + self.debug.maximum_reconnection_attempts } pub fn get_message_retrieval_limit(&self) -> u16 { @@ -397,11 +474,26 @@ impl Config { pub fn get_stored_messages_filename_length(&self) -> u16 { self.debug.stored_messages_filename_length } + + pub fn get_cache_entry_ttl(&self) -> Duration { + self.debug.cache_entry_ttl + } + + pub fn get_version(&self) -> &str { + &self.gateway.version + } + + pub fn get_incentives_address(&self) -> Option { + self.gateway.incentives_address.clone() + } } #[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] pub struct Gateway { + /// Version of the gateway for which this configuration was created. + #[serde(default = "missing_string_value")] + version: String, + /// ID specifies the human readable ID of this particular gateway. id: String, @@ -423,29 +515,34 @@ pub struct Gateway { /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - /// Directory server to which the server will be reporting their presence data. - presence_directory_server: String, + /// Validator server to which the node will be reporting their presence data. + #[serde(default = "missing_string_value")] + validator_rest_url: String, /// nym_home_directory specifies absolute path to the home nym gateways directory. /// It is expected to use default value and hence .toml file should not redefine this field. nym_root_directory: PathBuf, + + /// Optional, if participating in the incentives program, payment address. + #[serde(deserialize_with = "deserialize_option_string", default)] + incentives_address: Option, } impl Gateway { fn default_private_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_sphinx.pem") + Config::default_data_directory(id).join("private_sphinx.pem") } fn default_public_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_sphinx.pem") + Config::default_data_directory(id).join("public_sphinx.pem") } fn default_private_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_identity.pem") + Config::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_identity.pem") + Config::default_data_directory(id).join("public_identity.pem") } fn default_location() -> String { @@ -456,14 +553,16 @@ impl Gateway { impl Default for Gateway { fn default() -> Self { Gateway { + version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), location: Self::default_location(), private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - presence_directory_server: DEFAULT_DIRECTORY_SERVER.to_string(), + validator_rest_url: DEFAULT_VALIDATOR_REST_ENDPOINT.to_string(), nym_root_directory: Config::default_root_directory(), + incentives_address: None, } } } @@ -520,11 +619,11 @@ pub struct ClientsEndpoint { impl ClientsEndpoint { fn default_inboxes_directory(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("inboxes") + Config::default_data_directory(id).join("inboxes") } fn default_ledger_path(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("client_ledger.sled") + Config::default_data_directory(id).join("client_ledger.sled") } } @@ -556,20 +655,37 @@ impl Default for Logging { pub struct Debug { /// Initial value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - /// The provided value is interpreted as milliseconds. - packet_forwarding_initial_backoff: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + packet_forwarding_initial_backoff: Duration, /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - /// The provided value is interpreted as milliseconds. - packet_forwarding_maximum_backoff: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + packet_forwarding_maximum_backoff: Duration, /// Timeout for establishing initial connection when trying to forward a sphinx packet. - /// The provider value is interpreted as milliseconds. - initial_connection_timeout: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + initial_connection_timeout: Duration, /// Delay between each subsequent presence data being sent. - presence_sending_delay: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + presence_sending_delay: Duration, + + /// Maximum number of retries node is going to attempt to re-establish existing connection + /// to another node when forwarding sphinx packets. + maximum_reconnection_attempts: u32, /// Length of filenames for new client messages. stored_messages_filename_length: u16, @@ -578,6 +694,13 @@ pub struct Debug { /// if there are no real messages, dummy ones are create to always return /// `message_retrieval_limit` total messages message_retrieval_limit: u16, + + /// Duration for which a cached vpn processing result is going to get stored for. + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + cache_entry_ttl: Duration, } impl Default for Debug { @@ -587,28 +710,10 @@ impl Default for Debug { packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, + maximum_reconnection_attempts: DEFAULT_MAXIMUM_RECONNECTION_ATTEMPTS, stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + cache_entry_ttl: DEFAULT_CACHE_ENTRY_TTL, } } } - -#[cfg(test)] -mod gateway_config { - use super::*; - - #[test] - fn after_saving_default_config_the_loaded_one_is_identical() { - // need to figure out how to do something similar but without touching the disk - // or the file system at all... - let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); - let default_config = Config::default().with_id("foomp".to_string()); - default_config - .save_to_file(Some(temp_location.clone())) - .unwrap(); - - let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); - - assert_eq!(default_config, loaded_config); - } -} diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 55e2c230a0..a66b5ef782 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -24,6 +24,9 @@ pub(crate) fn config_template() -> &'static str { ##### main base mixnode config options ##### [gateway] +# Version of the gateway for which this configuration was created. +version = '{{ gateway.version }}' + # Human readable ID of this particular gateway. id = '{{ gateway.id }}' @@ -45,8 +48,11 @@ private_sphinx_key_file = '{{ gateway.private_sphinx_key_file }}' # Path to file containing public sphinx key. public_sphinx_key_file = '{{ gateway.public_sphinx_key_file }}' -# Directory server to which the server will be reporting their presence data. -presence_directory_server = '{{ gateway.presence_directory_server }}' +# Optional, if participating in the incentives program, payment address. +incentives_address = '{{ gateway.incentives_address }}' + +# Validator server to which the node will be reporting their presence data. +validator_rest_url = '{{ gateway.validator_rest_url }}' # nym_home_directory specifies absolute path to the home nym gateway directory. # It is expected to use default value and hence .toml file should not redefine this field. diff --git a/gateway/src/main.rs b/gateway/src/main.rs index c921aaa100..e9bdf86443 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -14,7 +14,6 @@ use clap::{App, ArgMatches}; -pub mod built_info; mod commands; mod config; mod node; @@ -25,11 +24,12 @@ fn main() { println!("{}", banner()); let arg_matches = App::new("Nym Mixnet Gateway") - .version(built_info::PKG_VERSION) + .version(env!("CARGO_PKG_VERSION")) .author("Nymtech") .about("Implementation of the Nym Mixnet Gateway") .subcommand(commands::init::command_args()) .subcommand(commands::run::command_args()) + .subcommand(commands::upgrade::command_args()) .get_matches(); execute(arg_matches); @@ -39,6 +39,7 @@ fn execute(matches: ArgMatches) { match matches.subcommand() { ("init", Some(m)) => commands::init::execute(m), ("run", Some(m)) => commands::run::execute(m), + ("upgrade", Some(m)) => commands::upgrade::execute(m), _ => println!("{}", usage()), } } @@ -60,7 +61,7 @@ fn banner() -> String { (gateway - version {:}) "#, - built_info::PKG_VERSION + env!("CARGO_PKG_VERSION") ) } diff --git a/gateway/src/node/client_handling/clients_handler.rs b/gateway/src/node/client_handling/clients_handler.rs index d39cd40bff..689736dd16 100644 --- a/gateway/src/node/client_handling/clients_handler.rs +++ b/gateway/src/node/client_handling/clients_handler.rs @@ -114,7 +114,7 @@ impl ClientsHandler { // NOTE: THIS IGNORES MESSAGE RETRIEVAL LIMIT AND TAKES EVERYTHING! let all_stored_messages = match self .clients_inbox_storage - .retrieve_all_client_messages(client_address.clone()) + .retrieve_all_client_messages(client_address) .await { Ok(msgs) => msgs, @@ -173,7 +173,7 @@ impl ClientsHandler { if self .clients_ledger - .insert_shared_key(derived_shared_key, address.clone()) + .insert_shared_key(derived_shared_key, address) .unwrap() .is_some() { @@ -181,11 +181,7 @@ impl ClientsHandler { "Client {:?} was already registered before!", address.to_base58_string() ) - } else if let Err(e) = self - .clients_inbox_storage - .create_storage_dir(address.clone()) - .await - { + } else if let Err(e) = self.clients_inbox_storage.create_storage_dir(address).await { error!("We failed to create inbox directory for the client -{:?}\nReverting stored shared key...", e); // we must revert our changes if this operation failed self.clients_ledger.remove_shared_key(&address).unwrap(); diff --git a/gateway/src/node/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs index 8adde3292a..70d472c611 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -18,7 +18,6 @@ use crate::node::client_handling::clients_handler::{ use crate::node::client_handling::websocket::message_receiver::{ MixMessageReceiver, MixMessageSender, }; -use crate::node::mixnet_handling::sender::OutboundMixMessageSender; use crypto::asymmetric::identity; use futures::{ channel::{mpsc, oneshot}, @@ -31,6 +30,7 @@ use gateway_requests::registration::handshake::{gateway_handshake, SharedKeys, D use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse}; use gateway_requests::BinaryResponse; use log::*; +use mixnet_client::forwarder::MixForwardingSender; use nymsphinx::DestinationAddressBytes; use std::convert::TryFrom; use std::sync::Arc; @@ -54,10 +54,7 @@ enum SocketStream { impl SocketStream { fn is_websocket(&self) -> bool { - match self { - SocketStream::UpgradedWebSocket(_) => true, - _ => false, - } + matches!(self, SocketStream::UpgradedWebSocket(_)) } } @@ -65,7 +62,7 @@ pub(crate) struct Handle { remote_address: Option, shared_key: Option, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, socket_connection: SocketStream, local_identity: Arc, @@ -77,7 +74,7 @@ impl Handle { pub(crate) fn new( conn: S, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, local_identity: Arc, ) -> Self { Handle { @@ -194,7 +191,7 @@ impl Handle { // announced hence we do not need to send 'disconnect' message if let Some(addr) = self.remote_address.as_ref() { self.clients_handler_sender - .unbounded_send(ClientsHandlerRequest::Disconnect(addr.clone())) + .unbounded_send(ClientsHandlerRequest::Disconnect(*addr)) .unwrap(); } } @@ -212,14 +209,8 @@ impl Handle { Err(e) => ServerResponse::new_error(e.to_string()), Ok(request) => match request { // currently only a single type exists - BinaryRequest::ForwardSphinx { - address, - sphinx_packet, - } => { - // we know data has correct size (but nothing else besides of it) - self.outbound_mix_sender - .unbounded_send((address, sphinx_packet)) - .unwrap(); + BinaryRequest::ForwardSphinx(mix_packet) => { + self.outbound_mix_sender.unbounded_send(mix_packet).unwrap(); ServerResponse::Send { status: true } } }, @@ -260,7 +251,7 @@ impl Handle { let (res_sender, res_receiver) = oneshot::channel(); let clients_handler_request = ClientsHandlerRequest::Authenticate( - address.clone(), + address, encrypted_address, iv, mix_sender, @@ -326,7 +317,7 @@ impl Handle { let (res_sender, res_receiver) = oneshot::channel(); let clients_handler_request = ClientsHandlerRequest::Register( - remote_address.clone(), + remote_address, derived_shared_key.clone(), mix_sender, res_sender, diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index e3acdbd9b4..2c353c0c82 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -14,9 +14,9 @@ use crate::node::client_handling::clients_handler::ClientsHandlerRequestSender; use crate::node::client_handling::websocket::connection_handler::Handle; -use crate::node::mixnet_handling::sender::OutboundMixMessageSender; use crypto::asymmetric::identity; use log::*; +use mixnet_client::forwarder::MixForwardingSender; use std::net::SocketAddr; use std::sync::Arc; use tokio::task::JoinHandle; @@ -37,7 +37,7 @@ impl Listener { pub(crate) async fn run( &mut self, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, ) { info!("Starting websocket listener at {}", self.address); let mut tcp_listener = tokio::net::TcpListener::bind(self.address) @@ -66,7 +66,7 @@ impl Listener { pub(crate) fn start( mut self, clients_handler_sender: ClientsHandlerRequestSender, - outbound_mix_sender: OutboundMixMessageSender, + outbound_mix_sender: MixForwardingSender, ) -> JoinHandle<()> { tokio::spawn(async move { self.run(clients_handler_sender, outbound_mix_sender).await }) } diff --git a/gateway/src/node/mixnet_handling/mod.rs b/gateway/src/node/mixnet_handling/mod.rs index c4d03e42fa..b664445392 100644 --- a/gateway/src/node/mixnet_handling/mod.rs +++ b/gateway/src/node/mixnet_handling/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. pub(crate) mod receiver; -pub(crate) mod sender; pub(crate) use receiver::listener::Listener; pub(crate) use receiver::packet_processing::PacketProcessor; diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 3389e774ad..d31ad53e05 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -12,64 +12,240 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::node::client_handling::clients_handler::{ + ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, +}; +use crate::node::client_handling::websocket::message_receiver::MixMessageSender; use crate::node::mixnet_handling::receiver::packet_processing::PacketProcessor; +use crate::node::storage::inboxes::{ClientStorage, StoreData}; +use dashmap::DashMap; +use futures::channel::oneshot; use log::*; -use nymsphinx::framing::SphinxCodec; -use nymsphinx::SphinxPacket; +use mixnet_client::forwarder::MixForwardingSender; +use mixnode_common::cached_packet_processor::processor::ProcessedFinalHop; +use nymsphinx::forwarding::packet::MixPacket; +use nymsphinx::framing::codec::SphinxCodec; +use nymsphinx::framing::packet::FramedSphinxPacket; +use nymsphinx::DestinationAddressBytes; use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::TcpStream; use tokio::prelude::*; use tokio::stream::StreamExt; use tokio_util::codec::Framed; -pub(crate) struct Handle { - peer_address: SocketAddr, - framed_connection: Framed, +pub(crate) struct ConnectionHandler { packet_processor: PacketProcessor, + + // TODO: method for cache invalidation so that we wouldn't keep all stale channel references + // we could use our friend DelayQueue. Alternatively we could periodically check for if the + // channels are closed. + available_socket_senders_cache: DashMap, + client_store: ClientStorage, + clients_handler_sender: ClientsHandlerRequestSender, + ack_sender: MixForwardingSender, } -impl Handle -where - S: AsyncRead + AsyncWrite + Unpin + 'static, -{ - // for time being we assume handle is always constructed from raw socket. - // if we decide we want to change it, that's not too difficult +impl ConnectionHandler { pub(crate) fn new( - peer_address: SocketAddr, - conn: S, packet_processor: PacketProcessor, + clients_handler_sender: ClientsHandlerRequestSender, + client_store: ClientStorage, + + ack_sender: MixForwardingSender, ) -> Self { - // we expect only to receive sphinx packets on this socket, so let's frame it here - let framed = Framed::new(conn, SphinxCodec); - Handle { - peer_address, - framed_connection: framed, + ConnectionHandler { packet_processor, + available_socket_senders_cache: DashMap::new(), + client_store, + clients_handler_sender, + ack_sender, } } - async fn process_received_packet( - sphinx_packet: SphinxPacket, - mut packet_processor: PacketProcessor, - ) { - match packet_processor.process_sphinx_packet(sphinx_packet).await { - Ok(_) => trace!("successfully processed [and forwarded/stored] a final hop packet"), - Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), + pub(crate) fn clone_without_cache(&self) -> Self { + // TODO: should this be even cloned? + let senders_cache = DashMap::with_capacity(self.available_socket_senders_cache.capacity()); + for element_guard in self.available_socket_senders_cache.iter() { + let (k, v) = element_guard.pair(); + // TODO: this will be made redundant once there's some cache invalidator mechanism here + if !v.is_closed() { + senders_cache.insert(*k, v.clone()); + } + } + + ConnectionHandler { + packet_processor: self.packet_processor.clone_without_key_cache(), + available_socket_senders_cache: senders_cache, + client_store: self.client_store.clone(), + clients_handler_sender: self.clients_handler_sender.clone(), + ack_sender: self.ack_sender.clone(), } } - pub(crate) async fn start_handling(&mut self) { - while let Some(sphinx_packet) = self.framed_connection.next().await { - match sphinx_packet { - Ok(sphinx_packet) => { - // rather important TODO: - // we *really* need a worker pool here, because if we receive too many packets, - // we will spawn too many tasks and starve CPU due to context switching. - // (because presumably tokio has some concept of context switching in its - // scheduler) - tokio::spawn(Self::process_received_packet( - sphinx_packet, - self.packet_processor.clone(), - )); + fn try_push_message_to_client( + &self, + sender_channel: Option, + message: Vec, + ) -> Result<(), Vec> { + match sender_channel { + None => Err(message), + Some(sender_channel) => { + sender_channel + .unbounded_send(vec![message]) + // right now it's a "simpler" case here as we're only ever sending 1 message + // at the time, but the channel itself could accept arbitrary many messages at once + .map_err(|try_send_err| try_send_err.into_inner().pop().unwrap()) + } + } + } + + fn remove_stale_client_sender(&self, client_address: &DestinationAddressBytes) { + if !self.available_socket_senders_cache.remove(client_address) { + warn!( + "Tried to remove stale entry for non-existent client sender: {}", + client_address + ) + } + } + + async fn try_to_obtain_client_ws_message_sender( + &self, + client_address: DestinationAddressBytes, + ) -> Option { + let mut should_remove_stale = false; + if let Some(sender_ref) = self.available_socket_senders_cache.get(&client_address) { + let sender = sender_ref.value(); + if !sender.is_closed() { + return Some(sender.clone()); + } else { + should_remove_stale = true; + } + } + + // we want to do it outside the immutable borrow into the map + if should_remove_stale { + self.remove_stale_client_sender(&client_address) + } + + // if we got here it means that either we have no sender channel for this client or it's closed + // so we must refresh it from the source, i.e. ClientsHandler + let (res_sender, res_receiver) = oneshot::channel(); + let clients_handler_request = ClientsHandlerRequest::IsOnline(client_address, res_sender); + self.clients_handler_sender + .unbounded_send(clients_handler_request) + .unwrap(); // the receiver MUST BE alive + + let client_sender = match res_receiver.await.unwrap() { + ClientsHandlerResponse::IsOnline(client_sender) => client_sender, + _ => panic!("received response to wrong query!"), // again, this should NEVER happen + }?; + + // finally update the cache + if self + .available_socket_senders_cache + .insert(client_address, client_sender.clone()) + { + // this warning is harmless, but I want to see if it's realistically for it to even occur + warn!("Other thread already updated cache for client sender!") + } + + Some(client_sender) + } + + pub(crate) async fn store_processed_packet_payload( + &self, + client_address: DestinationAddressBytes, + message: Vec, + ) -> io::Result<()> { + debug!( + "Storing received message for {} on the disk...", + client_address + ); + + let store_data = StoreData::new(client_address, message); + self.client_store.store_processed_data(store_data).await + } + + fn forward_ack(&self, forward_ack: Option, client_address: DestinationAddressBytes) { + if let Some(forward_ack) = forward_ack { + trace!( + "Sending ack from packet for {} to {}", + client_address, + forward_ack.next_hop() + ); + + self.ack_sender.unbounded_send(forward_ack).unwrap(); + } + } + + async fn handle_processed_packet(&self, processed_final_hop: ProcessedFinalHop) { + let client_address = processed_final_hop.destination; + let message = processed_final_hop.message; + let forward_ack = processed_final_hop.forward_ack; + + let client_sender = self + .try_to_obtain_client_ws_message_sender(client_address) + .await; + + // we failed to push message directly to the client - it's probably offline. + // we should store it on the disk instead. + match self.try_push_message_to_client(client_sender, message) { + Err(unsent_plaintext) => match self + .store_processed_packet_payload(client_address, unsent_plaintext) + .await + { + Err(err) => error!("Failed to store client data - {}", err), + Ok(_) => trace!("Stored packet for {}", client_address), + }, + Ok(_) => trace!("Pushed received packet to {}", client_address), + } + + // if we managed to either push message directly to the [online] client or store it at + // its inbox, it means that it must exist at this gateway, hence we can send the + // received ack back into the network + self.forward_ack(forward_ack, client_address); + } + + async fn handle_received_packet(self: Arc, framed_sphinx_packet: FramedSphinxPacket) { + // + // TODO: here be replay attack detection - it will require similar key cache to the one in + // packet processor for vpn packets, + // question: can it also be per connection vs global? + // + + let processed_final_hop = match self + .packet_processor + .process_received(framed_sphinx_packet) + .await + { + Err(e) => { + debug!("We failed to process received sphinx packet - {:?}", e); + return; + } + Ok(processed_final_hop) => processed_final_hop, + }; + + self.handle_processed_packet(processed_final_hop).await + } + + pub(crate) async fn handle_connection(self, conn: TcpStream, remote: SocketAddr) { + debug!("Starting connection handler for {:?}", remote); + let this = Arc::new(self); + let mut framed_conn = Framed::new(conn, SphinxCodec); + while let Some(framed_sphinx_packet) = framed_conn.next().await { + match framed_sphinx_packet { + Ok(framed_sphinx_packet) => { + // TODO: benchmark spawning tokio task with full processing vs just processing it + // synchronously (without delaying inside of course, + // delay could be moved to a per-connection DelayQueue. The delay queue future + // could automatically just forward packet that is done being delayed) + // under higher load in single and multi-threaded situation. + // + // My gut feeling is saying that we might get some nice performance boost + // if we introduced the change + let this = Arc::clone(&this); + tokio::spawn(this.handle_received_packet(framed_sphinx_packet)); } Err(err) => { error!( @@ -80,6 +256,10 @@ where } } } - info!("Closing connection from {:?}", self.peer_address); + + info!( + "Closing connection from {:?}", + framed_conn.into_inner().peer_addr() + ); } } diff --git a/gateway/src/node/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs index 9532a37af5..36002e6fca 100644 --- a/gateway/src/node/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::node::mixnet_handling::receiver::{ - connection_handler::Handle, packet_processing::PacketProcessor, -}; +use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use log::*; use std::net::SocketAddr; use tokio::task::JoinHandle; @@ -23,12 +21,13 @@ pub(crate) struct Listener { address: SocketAddr, } +// TODO: this file is nearly identical to the one in mixnode impl Listener { pub(crate) fn new(address: SocketAddr) -> Self { Listener { address } } - pub(crate) async fn run(&mut self, packet_processor: PacketProcessor) { + pub(crate) async fn run(&mut self, connection_handler: ConnectionHandler) { info!("Starting mixnet listener at {}", self.address); let mut tcp_listener = tokio::net::TcpListener::bind(self.address) .await @@ -37,16 +36,17 @@ impl Listener { loop { match tcp_listener.accept().await { Ok((socket, remote_addr)) => { - trace!("received a socket connection from {}", remote_addr); - let mut handle = Handle::new(remote_addr, socket, packet_processor.clone()); - tokio::spawn(async move { handle.start_handling().await }); + let handler = connection_handler.clone_without_cache(); + tokio::spawn(handler.handle_connection(socket, remote_addr)); } Err(e) => warn!("failed to get client: {:?}", e), } } } - pub(crate) fn start(mut self, packet_processor: PacketProcessor) -> JoinHandle<()> { - tokio::spawn(async move { self.run(packet_processor).await }) + pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> { + info!("Running mix listener on {:?}", self.address.to_string()); + + tokio::spawn(async move { self.run(connection_handler).await }) } } diff --git a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs index bc87c08e72..3cb19bce21 100644 --- a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs @@ -12,285 +12,56 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::node::client_handling::clients_handler::{ - ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, -}; -use crate::node::client_handling::websocket::message_receiver::MixMessageSender; -use crate::node::mixnet_handling::sender::OutboundMixMessageSender; -use crate::node::storage::inboxes::{ClientStorage, StoreData}; use crypto::asymmetric::encryption; -use futures::channel::oneshot; -use futures::lock::Mutex; -use log::*; -use nymsphinx::acknowledgements::surb_ack::{SURBAck, SURBAckRecoveryError}; -use nymsphinx::params::packet_sizes::PacketSize; -use nymsphinx::{DestinationAddressBytes, Error as SphinxError, ProcessedPacket, SphinxPacket}; - -use std::collections::HashMap; -use std::io; -use std::sync::Arc; +use mixnode_common::cached_packet_processor::error::MixProcessingError; +pub use mixnode_common::cached_packet_processor::processor::MixProcessingResult; +use mixnode_common::cached_packet_processor::processor::{ + CachedPacketProcessor, ProcessedFinalHop, +}; +use nymsphinx::framing::packet::FramedSphinxPacket; +use tokio::time::Duration; #[derive(Debug)] -pub enum MixProcessingError { - ReceivedForwardHopError, - UnsupportedSphinxPacketSize(usize), - SphinxProcessingError(SphinxError), - IncorrectlyFormattedSURBAck(SURBAckRecoveryError), - IOError(io::Error), +pub enum GatewayProcessingError { + PacketProcessingError(MixProcessingError), + ForwardHopReceivedError, } -impl From for MixProcessingError { - // for time being just have a single error instance for all possible results of SphinxError - fn from(err: SphinxError) -> Self { - use MixProcessingError::*; +impl From for GatewayProcessingError { + fn from(e: MixProcessingError) -> Self { + use GatewayProcessingError::*; - SphinxProcessingError(err) - } -} - -impl From for MixProcessingError { - fn from(e: io::Error) -> Self { - use MixProcessingError::*; - - IOError(e) - } -} - -impl From for MixProcessingError { - fn from(err: SURBAckRecoveryError) -> Self { - use MixProcessingError::*; - - IncorrectlyFormattedSURBAck(err) + PacketProcessingError(e) } } // PacketProcessor contains all data required to correctly unwrap and store sphinx packets -#[derive(Clone)] pub struct PacketProcessor { - encryption_keys: Arc, - // TODO: later investigate some concurrent hashmap solutions or perhaps RWLocks. - // Right now Mutex is the simplest and fastest to implement approach - available_socket_senders_cache: Arc>>, - client_store: ClientStorage, - clients_handler_sender: ClientsHandlerRequestSender, - ack_sender: OutboundMixMessageSender, + inner_processor: CachedPacketProcessor, } impl PacketProcessor { - pub(crate) fn new( - encryption_keys: Arc, - clients_handler_sender: ClientsHandlerRequestSender, - client_store: ClientStorage, - ack_sender: OutboundMixMessageSender, - ) -> Self { + pub(crate) fn new(encryption_key: &encryption::PrivateKey, cache_entry_ttl: Duration) -> Self { PacketProcessor { - available_socket_senders_cache: Arc::new(Mutex::new(HashMap::new())), - clients_handler_sender, - client_store, - encryption_keys, - ack_sender, + inner_processor: CachedPacketProcessor::new(encryption_key.into(), cache_entry_ttl), } } - fn try_push_message_to_client( + pub(crate) fn clone_without_key_cache(&self) -> Self { + PacketProcessor { + inner_processor: self.inner_processor.clone_without_cache(), + } + } + + pub(crate) async fn process_received( &self, - sender_channel: Option, - message: Vec, - ) -> Result<(), Vec> { - match sender_channel { - None => Err(message), - Some(sender_channel) => { - sender_channel - .unbounded_send(vec![message]) - // right now it's a "simpler" case here as we're only ever sending 1 message - // at the time, but the channel itself could accept arbitrary many messages at once - .map_err(|try_send_err| try_send_err.into_inner().pop().unwrap()) + received: FramedSphinxPacket, + ) -> Result { + match self.inner_processor.process_received(received).await? { + MixProcessingResult::ForwardHop(..) => { + Err(GatewayProcessingError::ForwardHopReceivedError) } + MixProcessingResult::FinalHop(processed_final) => Ok(processed_final), } } - - async fn try_to_obtain_client_ws_message_sender( - &mut self, - client_address: DestinationAddressBytes, - ) -> Option { - let mut cache_guard = self.available_socket_senders_cache.lock().await; - - if let Some(sender) = cache_guard.get(&client_address) { - if !sender.is_closed() { - return Some(sender.clone()); - } else { - cache_guard.remove(&client_address); - } - } - - // do not block other readers to the cache while we are doing some blocking work here - drop(cache_guard); - - // if we got here it means that either we have no sender channel for this client or it's closed - // so we must refresh it from the source, i.e. ClientsHandler - let (res_sender, res_receiver) = oneshot::channel(); - let clients_handler_request = - ClientsHandlerRequest::IsOnline(client_address.clone(), res_sender); - self.clients_handler_sender - .unbounded_send(clients_handler_request) - .unwrap(); // the receiver MUST BE alive - - let client_sender = match res_receiver.await.unwrap() { - ClientsHandlerResponse::IsOnline(client_sender) => client_sender, - _ => panic!("received response to wrong query!"), // again, this should NEVER happen - }; - - client_sender.as_ref()?; - - let client_sender = client_sender.unwrap(); - // finally re-acquire the lock to update the cache - let mut cache_guard = self.available_socket_senders_cache.lock().await; - cache_guard.insert(client_address, client_sender.clone()); - - Some(client_sender) - } - - pub(crate) async fn store_processed_packet_payload( - &self, - client_address: DestinationAddressBytes, - message: Vec, - ) -> io::Result<()> { - debug!( - "Storing received packet for {:?} on the disk...", - client_address.to_base58_string() - ); - // we are temporarily ignoring and not storing obvious loop cover traffic messages to - // not cause our sfw-provider to run out of disk space too quickly. - // Eventually this is going to get removed and be replaced by a quota system described in: - // https://github.com/nymtech/nym/issues/137 - - // JS: I think this would never get called anyway, because if loop cover messages are sent - // it means client is online and hence all his messages should be pushed directly to him? - if nymsphinx::cover::is_cover(&message) { - debug!("Received a loop cover message - not going to store it"); - return Ok(()); - } - - let store_data = StoreData::new(client_address, message); - self.client_store.store_processed_data(store_data).await - } - - pub(crate) fn unwrap_sphinx_packet( - &self, - packet: SphinxPacket, - ) -> Result<(DestinationAddressBytes, Vec), MixProcessingError> { - match packet.process(&self.encryption_keys.as_ref().private_key().into()) { - Ok(ProcessedPacket::ProcessedPacketForwardHop(_, _, _)) => { - warn!("Received a forward hop message - those are not implemented for gateways"); - Err(MixProcessingError::ReceivedForwardHopError) - } - Ok(ProcessedPacket::ProcessedPacketFinalHop(client_address, _surb_id, payload)) => { - // in our current design, we do not care about the 'surb_id' in the header - // as it will always be empty anyway - let message = payload.recover_plaintext()?; - Ok((client_address, message)) - } - Err(e) => { - warn!("Failed to unwrap Sphinx packet: {:?}", e); - Err(MixProcessingError::SphinxProcessingError(e)) - } - } - } - - fn split_plaintext_into_ack_and_message( - &self, - mut extracted_plaintext: Vec, - ) -> (Vec, Vec) { - if extracted_plaintext.len() < SURBAck::len() { - // TODO: - // TODO: - // this is mostly for dev purposes to see if we receive something we did not mean to send - // but in an actual system, what should we do? abandon the whole packet? - // store client's data regardless? - // I'm going to leave this question open for until I've implemented reply SURBs - // as they will change the communication between client and gateway so this - // if statement might no longer make any sense - panic!("received packet without an ack"); - } - - let plaintext = extracted_plaintext.split_off(SURBAck::len()); - let ack_data = extracted_plaintext; - (ack_data, plaintext) - } - - pub(crate) async fn process_sphinx_packet( - &mut self, - sphinx_packet: SphinxPacket, - ) -> Result<(), MixProcessingError> { - // see if what we got now is an ack or normal packet - let packet_len = sphinx_packet.len(); - // TODO: micro-optimisations: - // 1. don't even try to unwrap the packet if it's not one of `PacketSize` variants - // 2. if client_address doesn't exist at this gateway, don't do any other work here - // (as stupid as this sounds, there's currently no easy way of directly checking if the - // client exists here) - let (client_address, plaintext) = self.unwrap_sphinx_packet(sphinx_packet)?; - let (routable_ack, plaintext) = match packet_len { - n if n == PacketSize::ACKPacket.size() => { - trace!("received an ack packet!"); - (None, plaintext) - } - n if n == PacketSize::RegularPacket.size() - || n == PacketSize::ExtendedPacket.size() => - { - trace!("received a normal packet!"); - let (ack_data, plaintext) = self.split_plaintext_into_ack_and_message(plaintext); - let (ack_first_hop, ack_packet) = SURBAck::try_recover_first_hop_packet(&ack_data)?; - (Some((ack_first_hop, ack_packet)), plaintext) - } - n => return Err(MixProcessingError::UnsupportedSphinxPacketSize(n)), - }; - - let client_sender = self - .try_to_obtain_client_ws_message_sender(client_address.clone()) - .await; - - if let Err(unsent_plaintext) = self.try_push_message_to_client(client_sender, plaintext) { - // means we failed to push message directly to the client (it might be offline) - // but we don't want to store an ack message for him - he won't be able to decode - // it anyway. - // TODO: after keybase discussion we *might* want to store them after all - if routable_ack.is_none() { - trace!("Received an ack for offline client - won't try storing it"); - return Ok(()); - } - - if let Err(io_err) = self - .store_processed_packet_payload(client_address.clone(), unsent_plaintext) - .await - { - return Err(io_err.into()); - } else { - trace!( - "Managed to store packet for {:?} on the disk", - client_address.to_base58_string() - ); - } - } else { - trace!( - "Managed to push received packet for {:?} to websocket connection!", - client_address.to_base58_string() - ); - } - - // if we managed to either push message directly to the [online] client or store it at - // it's inbox, it means that it must exist at this gateway, hence we can send the - // received ack back into the network - if let Some((ack_first_hop, ack_packet)) = routable_ack { - trace!( - "Sending an ack back into the network. The first hop is {:?}", - ack_first_hop - ); - self.ack_sender - .unbounded_send((ack_first_hop, ack_packet)) - .unwrap(); - } - - Ok(()) - } } diff --git a/gateway/src/node/mixnet_handling/sender/mod.rs b/gateway/src/node/mixnet_handling/sender/mod.rs deleted file mode 100644 index 5bed92ef73..0000000000 --- a/gateway/src/node/mixnet_handling/sender/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// TODO: code is nearly identical to mixnode::node::packet_forwarding -> perhaps it should be put to common? - -use futures::channel::mpsc; -use futures::StreamExt; -use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::time::Duration; -use tokio::task::JoinHandle; - -pub(crate) type OutboundMixMessageSender = - mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>; -pub(crate) type OutboundMixMessageReceiver = - mpsc::UnboundedReceiver<(NymNodeRoutingAddress, SphinxPacket)>; - -pub(crate) struct PacketForwarder { - mixnet_client: mixnet_client::Client, - conn_tx: OutboundMixMessageSender, - conn_rx: OutboundMixMessageReceiver, -} - -impl PacketForwarder { - pub(crate) fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, - ) -> PacketForwarder { - let tcp_client_config = mixnet_client::Config::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - ); - - let (conn_tx, conn_rx) = mpsc::unbounded(); - - PacketForwarder { - mixnet_client: mixnet_client::Client::new(tcp_client_config), - conn_tx, - conn_rx, - } - } - - pub(crate) fn start(mut self) -> (JoinHandle<()>, OutboundMixMessageSender) { - let sender_channel = self.conn_tx.clone(); - ( - tokio::spawn(async move { - while let Some((address, packet)) = self.conn_rx.next().await { - trace!("Going to forward packet to {:?}", address); - // as a mix node we don't care about responses, we just want to fire packets - // as quickly as possible - self.mixnet_client - .send(address, packet, false) - .await - .unwrap(); - // if we're not waiting for response, we MUST get an Ok - } - }), - sender_channel, - ) - } -} diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index e0a2dc3fa5..c0e2a459ad 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -15,11 +15,11 @@ use crate::config::Config; use crate::node::client_handling::clients_handler::{ClientsHandler, ClientsHandlerRequestSender}; use crate::node::client_handling::websocket; -use crate::node::mixnet_handling::sender::{OutboundMixMessageSender, PacketForwarder}; +use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::storage::{inboxes, ClientLedger}; use crypto::asymmetric::{encryption, identity}; -use directory_client::DirectoryClient; use log::*; +use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; use std::sync::Arc; use tokio::runtime::Runtime; @@ -65,24 +65,30 @@ impl Gateway { fn start_mix_socket_listener( &self, clients_handler_sender: ClientsHandlerRequestSender, - ack_sender: OutboundMixMessageSender, + ack_sender: MixForwardingSender, ) { info!("Starting mix socket listener..."); let packet_processor = mixnet_handling::PacketProcessor::new( - Arc::clone(&self.encryption_keys), + self.encryption_keys.private_key(), + self.config.get_cache_entry_ttl(), + ); + + let connection_handler = ConnectionHandler::new( + packet_processor, clients_handler_sender, self.client_inbox_storage.clone(), ack_sender, ); - mixnet_handling::Listener::new(self.config.get_mix_listening_address()) - .start(packet_processor); + let listener = mixnet_handling::Listener::new(self.config.get_mix_listening_address()); + + listener.start(connection_handler); } fn start_client_websocket_listener( &self, - forwarding_channel: OutboundMixMessageSender, + forwarding_channel: MixForwardingSender, clients_handler_sender: ClientsHandlerRequestSender, ) { info!("Starting client [web]socket listener..."); @@ -94,16 +100,18 @@ impl Gateway { .start(clients_handler_sender, forwarding_channel); } - fn start_packet_forwarder(&self) -> OutboundMixMessageSender { + fn start_packet_forwarder(&self) -> MixForwardingSender { info!("Starting mix packet forwarder..."); - let (_, forwarding_channel) = PacketForwarder::new( + let (mut packet_forwarder, packet_sender) = PacketForwarder::new( self.config.get_packet_forwarding_initial_backoff(), self.config.get_packet_forwarding_maximum_backoff(), self.config.get_initial_connection_timeout(), - ) - .start(); - forwarding_channel + self.config.get_packet_forwarding_max_reconnections(), + ); + + tokio::spawn(async move { packet_forwarder.run().await }); + packet_sender } fn start_clients_handler(&self) -> ClientsHandlerRequestSender { @@ -116,20 +124,6 @@ impl Gateway { clients_handler_sender } - fn start_presence_notifier(&self) { - info!("Starting presence notifier..."); - let notifier_config = presence::NotifierConfig::new( - self.config.get_location(), - self.config.get_presence_directory_server(), - self.config.get_mix_announce_address(), - self.config.get_clients_announce_address(), - self.identity.public_key().to_base58_string(), - self.encryption_keys.public_key().to_base58_string(), - self.config.get_presence_sending_delay(), - ); - presence::Notifier::new(notifier_config).start(); - } - async fn wait_for_interrupt(&self) { if let Err(e) = tokio::signal::ctrl_c().await { error!( @@ -140,26 +134,35 @@ impl Gateway { println!( "Received SIGINT - the gateway will terminate now (threads are not YET nicely stopped)" ); + if let Err(err) = presence::unregister_with_validator( + self.config.get_validator_rest_endpoint(), + self.identity.public_key().to_base58_string(), + ) + .await + { + error!("failed to unregister with validator... - {:?}", err) + } } async fn check_if_same_ip_gateway_exists(&self) -> Option { let announced_mix_host = self.config.get_mix_announce_address(); let announced_clients_host = self.config.get_clients_announce_address(); - let directory_client_cfg = - directory_client::Config::new(self.config.get_presence_directory_server()); - let topology = directory_client::Client::new(directory_client_cfg) + let validator_client_config = + validator_client::Config::new(self.config.get_validator_rest_endpoint()); + let validator_client = validator_client::Client::new(validator_client_config); + let topology = validator_client .get_topology() .await - .expect("Failed to retrieve network topology"); + .expect("failed to grab network topology"); - let existing_gateways = topology.gateway_nodes; + let existing_gateways = topology.gateways; existing_gateways .iter() .find(|node| { - node.mixnet_listener == announced_mix_host - || node.client_listener == announced_clients_host + node.mixnet_listener() == announced_mix_host + || node.clients_listener() == announced_clients_host }) - .map(|node| node.identity_key.clone()) + .map(|node| node.identity()) } // Rather than starting all futures with explicit `&Handle` argument, let's see how it works @@ -167,19 +170,35 @@ impl Gateway { // Basically more or less equivalent of using #[tokio::main] attribute. pub fn run(&mut self) { info!("Starting nym gateway!"); + let mut runtime = Runtime::new().unwrap(); runtime.block_on(async { - - if let Some(duplicate_gateway_key) = self.check_if_same_ip_gateway_exists().await { - error!( - "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", - duplicate_gateway_key - ); - return; + if let Some(duplicate_node_key) = self.check_if_same_ip_gateway_exists().await { + if duplicate_node_key == self.identity.public_key().to_base58_string() { + warn!("We seem to have not unregistered after going offline - there's a node with identical identity and announce-host as us registered.") + } else { + error!( + "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", + duplicate_node_key + ); + return; + } } - + if let Err(err) = presence::register_with_validator( + self.config.get_validator_rest_endpoint(), + self.config.get_mix_announce_address(), + self.config.get_clients_announce_address(), + self.identity.public_key().to_base58_string(), + self.encryption_keys.public_key().to_base58_string(), + self.config.get_version().to_string(), + self.config.get_location(), + self.config.get_incentives_address() + ).await { + error!("failed to register with the validator - {:?}", err); + return + } let mix_forwarding_channel = self.start_packet_forwarder(); let clients_handler_sender = self.start_clients_handler(); @@ -187,8 +206,6 @@ impl Gateway { self.start_mix_socket_listener(clients_handler_sender.clone(), mix_forwarding_channel.clone()); self.start_client_websocket_listener(mix_forwarding_channel, clients_handler_sender); - self.start_presence_notifier(); - info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!"); self.wait_for_interrupt().await diff --git a/gateway/src/node/presence.rs b/gateway/src/node/presence.rs new file mode 100644 index 0000000000..6b67222a88 --- /dev/null +++ b/gateway/src/node/presence.rs @@ -0,0 +1,54 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use validator_client::models::gateway::GatewayRegistrationInfo; +use validator_client::ValidatorClientError; + +// there's no point in keeping the validator client persistently as it might be literally hours or days +// before it's used again +pub(crate) async fn register_with_validator( + validator_endpoint: String, + mix_host: String, + clients_host: String, + identity_key: String, + sphinx_key: String, + version: String, + location: String, + incentives_address: Option, +) -> Result<(), ValidatorClientError> { + let config = validator_client::Config::new(validator_endpoint); + let validator_client = validator_client::Client::new(config); + + let registration_info = GatewayRegistrationInfo::new( + mix_host, + clients_host, + identity_key, + sphinx_key, + version, + location, + incentives_address, + ); + + validator_client.register_gateway(registration_info).await +} + +pub(crate) async fn unregister_with_validator( + validator_endpoint: String, + identity_key: String, +) -> Result<(), ValidatorClientError> { + let config = validator_client::Config::new(validator_endpoint); + let validator_client = validator_client::Client::new(config); + + validator_client.unregister_node(&identity_key).await +} diff --git a/gateway/src/node/presence/mod.rs b/gateway/src/node/presence/mod.rs deleted file mode 100644 index 7a6e32a453..0000000000 --- a/gateway/src/node/presence/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::built_info; -use directory_client::presence::gateways::GatewayPresence; -use directory_client::DirectoryClient; -use log::{error, trace}; -use std::time::Duration; -use tokio::task::JoinHandle; - -pub(crate) struct NotifierConfig { - location: String, - directory_server: String, - mix_announce_host: String, - clients_announce_host: String, - identity_string: String, - sphinx_key_string: String, - sending_delay: Duration, -} - -impl NotifierConfig { - pub(crate) fn new( - location: String, - directory_server: String, - mix_announce_host: String, - clients_announce_host: String, - identity_string: String, - sphinx_key_string: String, - sending_delay: Duration, - ) -> Self { - NotifierConfig { - location, - directory_server, - mix_announce_host, - clients_announce_host, - identity_string, - sphinx_key_string, - sending_delay, - } - } -} - -pub(crate) struct Notifier { - location: String, - net_client: directory_client::Client, - sending_delay: Duration, - client_listener: String, - mixnet_listener: String, - identity: String, - sphinx_key: String, -} - -impl Notifier { - pub(crate) fn new(config: NotifierConfig) -> Notifier { - let directory_client_cfg = directory_client::Config { - base_url: config.directory_server, - }; - let net_client = directory_client::Client::new(directory_client_cfg); - - Notifier { - net_client, - location: config.location, - client_listener: config.clients_announce_host, - mixnet_listener: config.mix_announce_host, - identity: config.identity_string, - sphinx_key: config.sphinx_key_string, - sending_delay: config.sending_delay, - } - } - - async fn make_presence(&self) -> GatewayPresence { - GatewayPresence { - location: self.location.clone(), - client_listener: self.client_listener.clone(), - mixnet_listener: self.mixnet_listener.clone(), - identity_key: self.identity.clone(), - sphinx_key: self.sphinx_key.clone(), - last_seen: 0, - version: built_info::PKG_VERSION.to_string(), - } - } - - async fn notify(&self, presence: GatewayPresence) { - match self.net_client.post_gateway_presence(presence).await { - Err(err) => error!("failed to send presence - {:?}", err), - Ok(_) => trace!("sent presence information"), - } - } - - pub fn start(self) -> JoinHandle<()> { - tokio::spawn(async move { - loop { - // set the deadline in the future - let sending_delay = tokio::time::delay_for(self.sending_delay); - let presence = self.make_presence().await; - self.notify(presence).await; - // wait for however much is left - sending_delay.await; - } - }) - } -} diff --git a/mixnode/Cargo.lock b/mixnode/Cargo.lock deleted file mode 100644 index 8e2c586d7a..0000000000 --- a/mixnode/Cargo.lock +++ /dev/null @@ -1,2371 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "adler32" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "aes-ctr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ctr 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aes-soft" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aesni" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aho-corasick" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "arc-swap" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "arrayref" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "backtrace" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "blake2b_simd" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "block-cipher-trait" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "built" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bytes" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cc" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "chrono" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "clear_on_drop" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cookie" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cookie_store" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "publicsuffix 1.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation-sys" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "crc32fast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-queue" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ctr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "curve25519-dalek" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dirs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dirs-sys" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dtoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "encoding_rs" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "error-chain" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "failure" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "failure_derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "flate2" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-channel" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-executor" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-io" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-macro" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-sink" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-task" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "generic-array" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "getrandom" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "git2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "h2" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hermit-abi" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hex" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "hkdf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "http-body" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "hyper" -version = "0.12.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "hyper-tls" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "indexmap" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "input_buffer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "jobserver" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libgit2-sys" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libz-sys" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lock_api" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memchr" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memoffset" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mime" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "mime_guess" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-named-pipes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-uds" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "native-tls" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "net2" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-integer" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-traits" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num_cpus" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nym-client" -version = "0.2.0" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "sfw-provider-requests 0.1.0", - "sphinx 0.1.0", - "tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tungstenite 0.10.0 (git+https://github.com/dbcfd/tokio-tungstenite?rev=6dc2018cbfe8fe7ddd75ff977343086503135b38)", - "tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nym-mixnode" -version = "0.2.0" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nym-client 0.2.0", - "sphinx 0.1.0", - "tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "openssl" -version = "0.10.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl-probe" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "openssl-sys" -version = "0.9.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pem" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pin-project" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pin-project-lite" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pin-utils" -version = "0.1.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pkg-config" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "proc-macro-hack" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "proc-macro-nested" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "proc-macro2" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "publicsuffix" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_distr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "redox_users" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "remove_dir_all" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "reqwest" -version = "0.9.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", - "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rust-argon2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ryu" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "schannel" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scopeguard" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "security-framework" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "security-framework-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_derive" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_json" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_urlencoded" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sfw-provider-requests" -version = "0.1.0" -dependencies = [ - "sphinx 0.1.0", -] - -[[package]] -name = "sha-1" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sha2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "signal-hook-registry" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "smallvec" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "smallvec" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "socket2" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sphinx" -version = "0.1.0" -dependencies = [ - "aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hkdf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lioness 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "stream-cipher" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "string" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "subtle" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "syn" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "synstructure" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tempfile" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-macros 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-executor" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-io" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-sync" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-timer" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.10.0" -source = "git+https://github.com/dbcfd/tokio-tungstenite?rev=6dc2018cbfe8fe7ddd75ff977343086503135b38#6dc2018cbfe8fe7ddd75ff977343086503135b38" -dependencies = [ - "futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "toml" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "try_from" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tungstenite" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "input_buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "typenum" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "url" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "utf-8" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "uuid" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "vcpkg" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "version_check" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "want" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasi" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" -"checksum aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee" -"checksum aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" -"checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" -"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" -"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" -"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" -"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum blake2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -"checksum blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0" -"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" -"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -"checksum built 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2315cfb416f86e05360edc950b1d7d25ecfb00f7f8eba60dbd7882a0f2e944" -"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" -"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -"checksum bytes 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c85319f157e4e26c703678e68e26ab71a46c0199286fa670b21cc9fec13d895" -"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -"checksum cc 1.0.47 (registry+https://github.com/rust-lang/crates.io-index)" = "aa87058dce70a3ff5621797f1506cb837edd02ac4c0ae642b4542dce802908b8" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum chacha 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" -"checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" -"checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" -"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" -"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" -"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" -"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" -"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" -"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -"checksum ctr 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "022cd691704491df67d25d006fe8eca083098253c4d43516c2206479c58c6736" -"checksum curve25519-dalek 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" -"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" -"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" -"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" -"checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" -"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" -"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" -"checksum futures 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" -"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" -"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" -"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" -"checksum futures-io 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" -"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" -"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" -"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" -"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" -"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" -"checksum git2 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c1af51ea8a906616af45a4ce78eacf25860f7a13ae7bf8a814693f0f4037a26" -"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" -"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" -"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e" -"checksum hkdf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" -"checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" -"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" -"checksum hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)" = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6" -"checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" -"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" -"checksum input_buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e1b822cc844905551931d6f81608ed5f50a79c1078a4e2b4d42dbc7c1eedfbf" -"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" -"checksum jobserver 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b1d42ef453b30b7387e113da1c83ab1605d90c5b4e0eb8e96d016ed3b8c160" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum keystream 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" -"checksum libgit2-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4870c781f6063efb83150cd22c1ddf6ecf58531419e7570cdcced46970f64a16" -"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" -"checksum lioness 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" -"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" -"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" -"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" -"checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" -"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" -"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" -"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" -"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" -"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" -"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" -"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" -"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" -"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" -"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -"checksum pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" -"checksum pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" -"checksum pin-project-lite 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0af6cbca0e6e3ce8692ee19fb8d734b641899e07b68eb73e9bbbd32f1703991" -"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" -"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" -"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" -"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" -"checksum publicsuffix 1.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" -"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_distr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" -"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" -"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" -"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "f88643aea3c1343c804950d7bf983bd2067f5ab59db6d613a08e05572f2714ab" -"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" -"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" -"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" -"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" -"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" -"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" -"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" -"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" -"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" -"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" -"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" -"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" -"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" -"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" -"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" -"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" -"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" -"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" -"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -"checksum tokio 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "beeef686ef92a222de07e089f455d9f8478bbba9651718f9e4b276babe829082" -"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" -"checksum tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ca6df436c42b0c3330a82d855d2ef017cd793090ad550a6bc2184f4b933532ab" -"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" -"checksum tokio-macros 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5795a71419535c6dcecc9b6ca95bdd3c2d6142f7e8343d7beb9923f129aa87e" -"checksum tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "6732fe6b53c8d11178dcb77ac6d9682af27fc6d4cb87789449152e5377377146" -"checksum tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" -"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -"checksum tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c32ffea4827978e9aa392d2f743d973c1dfa3730a2ed3f22ce1e6984da848c" -"checksum tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1739638e364e558128461fc1ad84d997702c8e31c2e6b18fb99842268199e827" -"checksum tokio-tungstenite 0.10.0 (git+https://github.com/dbcfd/tokio-tungstenite?rev=6dc2018cbfe8fe7ddd75ff977343086503135b38)" = "" -"checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" -"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" -"checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" -"checksum tungstenite 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8a0c2bd5aeb7dcd2bb32e472c8872759308495e5eccc942e929a513cd8d36110" -"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" -"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" -"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" -"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" -"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" -"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 3cc19b0b27..d65fa0cad0 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -1,7 +1,6 @@ [package] -build = "build.rs" name = "nym-mixnode" -version = "0.8.1" +version = "0.9.0" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] edition = "2018" @@ -14,6 +13,7 @@ curve25519-dalek = "2.0.0" dirs = "2.0.2" dotenv = "0.15.0" futures = "0.3.1" +humantime-serde = "1.0.1" log = "0.4" pretty_env_logger = "0.3" serde = { version = "1.0.104", features = ["derive"] } @@ -21,16 +21,13 @@ tokio = { version = "0.2", features = ["full"] } tokio-util = { version = "0.3.1", features = ["codec"] } ## internal -config = {path = "../common/config"} -crypto = {path = "../common/crypto"} -directory-client = { path = "../common/client-libs/directory-client" } +config = { path = "../common/config" } +crypto = { path = "../common/crypto" } +metrics-client = { path = "../common/client-libs/metrics-client" } mixnet-client = { path = "../common/client-libs/mixnet-client" } -nymsphinx = {path = "../common/nymsphinx" } -pemstore = {path = "../common/pemstore"} -topology = {path = "../common/topology"} - -[build-dependencies] -built = "0.4.3" - -[dev-dependencies] -tempfile = "3.1.0" \ No newline at end of file +mixnode-common = { path = "../common/mixnode-common" } +nymsphinx = { path = "../common/nymsphinx" } +pemstore = { path = "../common/pemstore" } +topology = { path = "../common/topology" } +validator-client = { path = "../common/client-libs/validator-client" } +version-checker = { path = "../common/version-checker" } diff --git a/mixnode/build.rs b/mixnode/build.rs deleted file mode 100644 index 56d753472c..0000000000 --- a/mixnode/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use built; - -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/mixnode/src/built_info.rs b/mixnode/src/built_info.rs deleted file mode 100644 index 38b46d59e6..0000000000 --- a/mixnode/src/built_info.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 09421e8c17..e178ee8f91 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -14,13 +14,12 @@ use crate::commands::override_config; use crate::config::persistence::pathfinder::MixNodePathfinder; +use crate::config::Config; use clap::{App, Arg, ArgMatches}; use config::NymConfig; -use crypto::asymmetric::encryption; -use directory_client::DirectoryClient; +use crypto::asymmetric::{encryption, identity}; use log::*; use nymsphinx::params::DEFAULT_NUM_MIX_HOPS; -use std::convert::TryInto; use tokio::runtime::Runtime; use topology::NymTopology; @@ -72,22 +71,26 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true), ) .arg( - Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the node is sending presence and metrics to") + Arg::with_name("validator") + .long("validator") + .help("REST endpoint of the validator the node is registering presence with") + .takes_value(true), + ) + .arg( + Arg::with_name("metrics-server") + .long("metrics-server") + .help("Server to which the node is sending all metrics data") + .takes_value(true), + ) + .arg( + Arg::with_name("incentives-address") + .long("incentives-address") + .help("Optional, if participating in the incentives program, payment address") .takes_value(true), ) } -fn show_incentives_url() { - println!("\n##### NOTE #####"); - println!( - "\nIf you would like to join our testnet incentives program, please visit https://nymtech.net/incentives" - ); - println!("\n\n"); -} - -async fn choose_layer(matches: &ArgMatches<'_>, directory_server: String) -> u64 { +async fn choose_layer(matches: &ArgMatches<'_>, validator_server: String) -> u64 { let max_layer = DEFAULT_NUM_MIX_HOPS; if let Some(layer) = matches.value_of("layer").map(|layer| layer.parse::()) { if let Err(err) = layer { @@ -100,14 +103,13 @@ async fn choose_layer(matches: &ArgMatches<'_>, directory_server: String) -> u64 } } - let directory_client_config = directory_client::Config::new(directory_server); - let directory_client = directory_client::Client::new(directory_client_config); - let topology: NymTopology = directory_client + let validator_client_config = validator_client::Config::new(validator_server); + let validator_client = validator_client::Client::new(validator_client_config); + let topology: NymTopology = validator_client .get_topology() .await .expect("failed to obtain initial network topology!") - .try_into() - .unwrap(); + .into(); let mut lowest_layer = (0, usize::max_value()); @@ -134,31 +136,53 @@ pub fn execute(matches: &ArgMatches) { rt.block_on(async { let id = matches.value_of("id").unwrap(); println!("Initialising mixnode {}...", id); - let mut config = crate::config::Config::new(id); + + let already_init = if Config::default_config_file_path(id).exists() { + println!("Mixnode \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id); + true + } else { + false + }; + + let mut config = Config::new(id); config = override_config(config, matches); - let layer = choose_layer(matches, config.get_presence_directory_server()).await; + let layer = choose_layer(matches, config.get_validator_rest_endpoint()).await; // TODO: I really don't like how we override config and are presumably done with it // only to change it here config = config.with_layer(layer); debug!("Choosing layer {}", config.get_layer()); - let sphinx_keys = encryption::KeyPair::new(); - let pathfinder = MixNodePathfinder::new_from_config(&config); - pemstore::store_keypair( - &sphinx_keys, - &pemstore::KeyPairPath::new( - pathfinder.private_encryption_key().to_owned(), - pathfinder.public_encryption_key().to_owned(), - ), - ) - .expect("Failed to save sphinx keys"); - println!("Saved mixnet sphinx keypair"); + // if node was already initialised, don't generate new keys + if !already_init { + let identity_keys = identity::KeyPair::new(); + let sphinx_keys = encryption::KeyPair::new(); + let pathfinder = MixNodePathfinder::new_from_config(&config); + pemstore::store_keypair( + &identity_keys, + &pemstore::KeyPairPath::new( + pathfinder.private_identity_key().to_owned(), + pathfinder.public_identity_key().to_owned(), + ), + ) + .expect("Failed to save identity keys"); + + pemstore::store_keypair( + &sphinx_keys, + &pemstore::KeyPairPath::new( + pathfinder.private_encryption_key().to_owned(), + pathfinder.public_encryption_key().to_owned(), + ), + ) + .expect("Failed to save sphinx keys"); + + println!("Saved mixnet identity and sphinx keypairs"); + } + let config_save_location = config.get_config_file_save_location(); config .save_to_file(None) .expect("Failed to save the config file"); println!("Saved configuration file to {:?}", config_save_location); println!("Mixnode configuration completed.\n\n\n"); - show_incentives_url(); }) } diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index d22c1215e1..d170240fb3 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -16,8 +16,9 @@ use crate::config::Config; use clap::ArgMatches; use nymsphinx::params::DEFAULT_NUM_MIX_HOPS; -pub mod init; -pub mod run; +pub(crate) mod init; +pub(crate) mod run; +pub(crate) mod upgrade; pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { let max_layer = DEFAULT_NUM_MIX_HOPS; @@ -46,8 +47,12 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_listening_port(port.unwrap()); } - if let Some(directory) = matches.value_of("directory") { - config = config.with_custom_directory(directory); + if let Some(validator) = matches.value_of("validator") { + config = config.with_custom_validator(validator); + } + + if let Some(metrics_server) = matches.value_of("metrics-server") { + config = config.with_custom_metrics_server(metrics_server); } if let Some(announce_host) = matches.value_of("announce-host") { @@ -72,5 +77,9 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_location(location); } + if let Some(incentives_address) = matches.value_of("incentives-address") { + config = config.with_incentives_address(incentives_address); + } + config } diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 54e58b45d9..b31324f2ae 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -17,7 +17,9 @@ use crate::config::{persistence::pathfinder::MixNodePathfinder, Config}; use crate::node::MixNode; use clap::{App, Arg, ArgMatches}; use config::NymConfig; -use crypto::asymmetric::encryption; +use crypto::asymmetric::{encryption, identity}; +use log::*; +use version_checker::is_minor_version_compatible; pub fn command_args<'a, 'b>() -> App<'a, 'b> { App::new("run") @@ -36,12 +38,6 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { .help("Optional geographical location of this node") .takes_value(true), ) - .arg( - Arg::with_name("config") - .long("config") - .help("Custom path to the nym-mixnode configuration file") - .takes_value(true), - ) .arg( Arg::with_name("layer") .long("layer") @@ -73,9 +69,15 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { .takes_value(true), ) .arg( - Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the node is sending presence and metrics to") + Arg::with_name("validator") + .long("validator") + .help("REST endpoint of the validator the node is registering presence with") + .takes_value(true), + ) + .arg( + Arg::with_name("metrics-server") + .long("metrics-server") + .help("Server to which the node is sending all metrics data") .takes_value(true), ) } @@ -95,6 +97,19 @@ fn special_addresses() -> Vec<&'static str> { vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"] } +fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair { + let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( + pathfinder.private_identity_key().to_owned(), + pathfinder.public_identity_key().to_owned(), + )) + .expect("Failed to read stored identity key files"); + println!( + "Public identity key: {}\n", + identity_keypair.public_key().to_base58_string() + ); + identity_keypair +} + fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair { let sphinx_keypair: encryption::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( pathfinder.private_encryption_key().to_owned(), @@ -108,18 +123,47 @@ fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair { sphinx_keypair } +// this only checks compatibility between config the binary. It does not take into consideration +// network version. It might do so in the future. +fn version_check(cfg: &Config) -> bool { + let binary_version = env!("CARGO_PKG_VERSION"); + let config_version = cfg.get_version(); + if binary_version != config_version { + warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version); + if is_minor_version_compatible(binary_version, config_version) { + info!("but they are still semver compatible. However, consider running the `upgrade` command"); + true + } else { + error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again"); + false + } + } else { + true + } +} + pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); println!("Starting mixnode {}...", id); - let mut config = - Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id)) - .expect("Failed to load config file"); + let mut config = match Config::load_from_file(id) { + Ok(cfg) => cfg, + Err(err) => { + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err); + return; + } + }; config = override_config(config, matches); + if !version_check(&config) { + error!("failed the local version check"); + return; + } + let pathfinder = MixNodePathfinder::new_from_config(&config); + let identity_keypair = load_identity_keys(&pathfinder); let sphinx_keypair = load_sphinx_keys(&pathfinder); let listening_ip_string = config.get_listening_address().ip().to_string(); @@ -129,11 +173,11 @@ pub fn execute(matches: &ArgMatches) { println!( "Directory server [presence]: {}", - config.get_presence_directory_server() + config.get_validator_rest_endpoint() ); println!( "Directory server [metrics]: {}", - config.get_metrics_directory_server() + config.get_metrics_server() ); println!( @@ -145,5 +189,5 @@ pub fn execute(matches: &ArgMatches) { config.get_announce_address() ); - MixNode::new(config, sphinx_keypair).run(); + MixNode::new(config, identity_keypair, sphinx_keypair).run(); } diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs new file mode 100644 index 0000000000..af8856865c --- /dev/null +++ b/mixnode/src/commands/upgrade.rs @@ -0,0 +1,228 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::config::{ + missing_string_value, Config, DEFAULT_METRICS_SERVER, DEFAULT_VALIDATOR_REST_ENDPOINT, +}; +use clap::{App, Arg, ArgMatches}; +use config::NymConfig; +use crypto::asymmetric::identity; +use std::fmt::Display; +use std::path::PathBuf; +use std::process; +use version_checker::{parse_version, Version}; + +fn print_start_upgrade(from: D1, to: D2) { + println!( + "\n==================\nTrying to upgrade mixnode from {} to {} ...", + from, to + ); +} + +fn print_failed_upgrade(from: D1, to: D2) { + eprintln!( + "Upgrade from {} to {} failed!\n==================\n", + from, to + ); +} + +fn print_successful_upgrade(from: D1, to: D2) { + println!( + "Upgrade from {} to {} was successful!\n==================\n", + from, to + ); +} + +fn pre_090_upgrade(from: &str, config: Config, matches: &ArgMatches) -> Config { + // note: current is guaranteed to not have any `build` information suffix (nor pre-release + // information), as this was asserted at the beginning of this command) + // + // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate + // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) + // this way we don't need to have all the crazy paths on how to upgrade from any version to any + // other version. We just upgrade one minor version at a time. + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + let to_version = if current.major == 0 && current.minor == 9 { + current + } else { + Version::new(0, 9, 0) + }; + + print_start_upgrade(&from, &to_version); + + // this is not extracted to separate function as you only have to manually pass version + // if upgrading from pre090 version + let from = match from.strip_prefix("v") { + Some(stripped) => stripped, + None => from, + }; + + let from = match from.strip_prefix("V") { + Some(stripped) => stripped, + None => from, + }; + + let from_version = parse_version(from).expect("invalid version provided!"); + if from_version.major == 0 && from_version.minor < 8 { + // technically this could be implemented, but is there any point in that? + eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead"); + print_failed_upgrade(&from_version, &to_version); + process::exit(1) + } + + if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { + eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); + print_failed_upgrade(&from_version, &to_version); + process::exit(1) + } + + if config.get_private_identity_key_file() != missing_string_value::() + || config.get_public_identity_key_file() != missing_string_value::() + { + eprintln!("existing config seems to have specified identity keys which were only introduced in 0.9.0! Can't perform upgrade."); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + } + + if config.get_metrics_server() != missing_string_value::() { + eprintln!("existing config seems to have specified new metrics-server endpoint which was only introduced in 0.9.0! Can't perform upgrade."); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + } + + if config.get_validator_rest_endpoint() != missing_string_value::() { + eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + } + + let mut upgraded_config = config + .with_custom_version(to_version.to_string().as_ref()) + .with_custom_metrics_server(DEFAULT_METRICS_SERVER) + .with_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + + if let Some(incentives_address) = matches.value_of("incentives address") { + upgraded_config = upgraded_config.with_incentives_address(incentives_address); + println!("Setting incentives address to {}", incentives_address); + } + + println!("Setting metrics server to {}", DEFAULT_METRICS_SERVER); + println!( + "Setting validator REST endpoint to to {}", + DEFAULT_VALIDATOR_REST_ENDPOINT + ); + + println!("Generating new identity..."); + let identity_keys = identity::KeyPair::new(); + upgraded_config.set_default_identity_keypair_paths(); + + if let Err(err) = pemstore::store_keypair( + &identity_keys, + &pemstore::KeyPairPath::new( + upgraded_config.get_private_identity_key_file(), + upgraded_config.get_public_identity_key_file(), + ), + ) { + eprintln!("Failed to save new identity key files! - {}", err); + process::exit(1); + } + + upgraded_config.save_to_file(None).unwrap_or_else(|err| { + eprintln!("failed to overwrite config file! - {:?}", err); + print_failed_upgrade(&from_version, &to_version); + process::exit(1); + }); + + print_successful_upgrade(from_version, to_version); + + upgraded_config +} + +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the mixnode") + .arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-mixnode we want to upgrade") + .takes_value(true) + .required(true), + ) + // the rest of arguments depend on the upgrade path + .arg(Arg::with_name("current version") + .long("current-version") + .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") + .takes_value(true) + ) + .arg(Arg::with_name("incentives address") + .long("incentives-address") + .help("Optional, if participating in the incentives program, payment address") + .takes_value(true) + ) +} + +pub fn execute(matches: &ArgMatches) { + let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + + // technically this is not a correct way of checking it as a released version might contain valid build identifiers + // however, we are not using them ourselves at the moment and hence it should be fine. + // if we change our mind, we could easily tweak this code + if current.is_prerelease() || !current.build.is_empty() { + eprintln!( + "Trying to upgrade to a non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + let id = matches.value_of("id").unwrap(); + + let mut existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + eprintln!("failed to load existing config file! - {:?}", err); + process::exit(1) + }); + + // versions fields were added in 0.9.0 + if existing_config.get_version() == missing_string_value::() { + let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { + eprintln!( + "trying to upgrade from pre v0.9.0 without providing current system version!" + ); + process::exit(1) + }); + + // upgrades up to 0.9.0 + existing_config = pre_090_upgrade(self_reported_version, existing_config, &matches); + } + + let config_version = Version::parse(existing_config.get_version()).unwrap_or_else(|err| { + eprintln!("failed to parse node version! - {:?}", err); + process::exit(1) + }); + + if config_version.is_prerelease() || !config_version.build.is_empty() { + eprintln!( + "Trying to upgrade to from non-released version {}. This is not supported!", + current + ); + process::exit(1) + } + + // here be upgrade path to 0.10.0 and beyond based on version number from config + if config_version == current { + println!("You're using the most recent version!"); + } else { + eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue!", config_version, current); + process::exit(1) + } +} diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index e98206bf1e..8ea27681f2 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -15,27 +15,33 @@ use crate::config::template::config_template; use config::NymConfig; use log::*; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{self, IntoDeserializer, Visitor}, + Deserialize, Deserializer, Serialize, +}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; -use std::time; +use std::time::Duration; pub mod persistence; mod template; +pub(crate) const MISSING_VALUE: &str = "MISSING VALUE"; + // 'MIXNODE' const DEFAULT_LISTENING_PORT: u16 = 1789; -const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net"; +pub(crate) const DEFAULT_VALIDATOR_REST_ENDPOINT: &str = + "http://testnet-validator1.nymtech.net:8081"; +pub(crate) const DEFAULT_METRICS_SERVER: &str = "http://testnet-metrics.nymtech.net:8080"; // 'DEBUG' -// where applicable, the below are defined in milliseconds -const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 10_000; // 10s -const DEFAULT_METRICS_SENDING_DELAY: u64 = 5_000; // 10s -const DEFAULT_METRICS_RUNNING_STATS_LOGGING_DELAY: u64 = 60_000; // 1min -const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: u64 = 10_000; // 10s -const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: u64 = 300_000; // 5min -const DEFAULT_INITIAL_CONNECTION_TIMEOUT: u64 = 1_500; // 1.5s +const DEFAULT_METRICS_RUNNING_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); +const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); +const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_CACHE_ENTRY_TTL: Duration = Duration::from_millis(30_000); +const DEFAULT_MAXIMUM_RECONNECTION_ATTEMPTS: u32 = 20; #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] @@ -53,10 +59,6 @@ impl NymConfig for Config { config_template() } - fn config_file_name() -> String { - "config.toml".to_string() - } - fn default_root_directory() -> PathBuf { dirs::home_dir() .expect("Failed to evaluate $HOME value") @@ -83,6 +85,69 @@ impl NymConfig for Config { } } +// custom function is defined to deserialize based on whether field contains a pre 0.9.0 +// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 +// +// TODO: when we get to refactoring down the line, this code can just be removed +// and all Duration fields could just have #[serde(with = "humantime_serde")] instead +// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, +// for argument sake, 0.11.0 out +fn deserialize_duration<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct DurationVisitor; + + impl<'de> Visitor<'de> for DurationVisitor { + type Value = Duration; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("u64 or a duration") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + self.visit_u64(value as u64) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(Duration::from_millis(Deserialize::deserialize( + value.into_deserializer(), + )?)) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + humantime_serde::deserialize(value.into_deserializer()) + } + } + + deserializer.deserialize_any(DurationVisitor) +} + +fn deserialize_option_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + if s.is_empty() { + Ok(None) + } else { + Ok(Some(s)) + } +} + +pub fn missing_string_value>() -> T { + MISSING_VALUE.to_string().into() +} + impl Config { pub fn new>(id: S) -> Self { Config::default().with_id(id) @@ -91,6 +156,20 @@ impl Config { // builder methods pub fn with_id>(mut self, id: S) -> Self { let id = id.into(); + if self + .mixnode + .private_identity_key_file + .as_os_str() + .is_empty() + { + self.mixnode.private_identity_key_file = + self::MixNode::default_private_identity_key_file(&id); + } + if self.mixnode.public_identity_key_file.as_os_str().is_empty() { + self.mixnode.public_identity_key_file = + self::MixNode::default_public_identity_key_file(&id); + } + if self.mixnode.private_sphinx_key_file.as_os_str().is_empty() { self.mixnode.private_sphinx_key_file = self::MixNode::default_private_sphinx_key_file(&id); @@ -99,6 +178,7 @@ impl Config { self.mixnode.public_sphinx_key_file = self::MixNode::default_public_sphinx_key_file(&id); } + self.mixnode.id = id; self } @@ -113,12 +193,13 @@ impl Config { self } - // if you want to use distinct servers for metrics and presence - // you need to do so in the config.toml file. - pub fn with_custom_directory>(mut self, directory_server: S) -> Self { - let directory_server_string = directory_server.into(); - self.mixnode.presence_directory_server = directory_server_string.clone(); - self.mixnode.metrics_directory_server = directory_server_string; + pub fn with_custom_validator>(mut self, validator: S) -> Self { + self.mixnode.validator_rest_url = validator.into(); + self + } + + pub fn with_custom_metrics_server>(mut self, server: S) -> Self { + self.mixnode.metrics_server_url = server.into(); self } @@ -197,6 +278,16 @@ impl Config { self } + pub fn with_custom_version(mut self, version: &str) -> Self { + self.mixnode.version = version.to_string(); + self + } + + pub fn with_incentives_address>(mut self, incentives_address: S) -> Self { + self.mixnode.incentives_address = Some(incentives_address.into()); + self + } + // getters pub fn get_config_file_save_location(&self) -> PathBuf { self.config_directory().join(Self::config_file_name()) @@ -206,6 +297,14 @@ impl Config { self.mixnode.location.clone() } + pub fn get_private_identity_key_file(&self) -> PathBuf { + self.mixnode.private_identity_key_file.clone() + } + + pub fn get_public_identity_key_file(&self) -> PathBuf { + self.mixnode.public_identity_key_file.clone() + } + pub fn get_private_sphinx_key_file(&self) -> PathBuf { self.mixnode.private_sphinx_key_file.clone() } @@ -214,24 +313,16 @@ impl Config { self.mixnode.public_sphinx_key_file.clone() } - pub fn get_presence_directory_server(&self) -> String { - self.mixnode.presence_directory_server.clone() + pub fn get_validator_rest_endpoint(&self) -> String { + self.mixnode.validator_rest_url.clone() } - pub fn get_presence_sending_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.presence_sending_delay) + pub fn get_metrics_server(&self) -> String { + self.mixnode.metrics_server_url.clone() } - pub fn get_metrics_directory_server(&self) -> String { - self.mixnode.metrics_directory_server.clone() - } - - pub fn get_metrics_sending_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.metrics_sending_delay) - } - - pub fn get_metrics_running_stats_logging_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.metrics_running_stats_logging_delay) + pub fn get_metrics_running_stats_logging_delay(&self) -> Duration { + self.debug.metrics_running_stats_logging_delay } pub fn get_layer(&self) -> u64 { @@ -246,22 +337,49 @@ impl Config { self.mixnode.announce_address.clone() } - pub fn get_packet_forwarding_initial_backoff(&self) -> time::Duration { - time::Duration::from_millis(self.debug.packet_forwarding_initial_backoff) + pub fn get_packet_forwarding_initial_backoff(&self) -> Duration { + self.debug.packet_forwarding_initial_backoff } - pub fn get_packet_forwarding_maximum_backoff(&self) -> time::Duration { - time::Duration::from_millis(self.debug.packet_forwarding_maximum_backoff) + pub fn get_packet_forwarding_maximum_backoff(&self) -> Duration { + self.debug.packet_forwarding_maximum_backoff } - pub fn get_initial_connection_timeout(&self) -> time::Duration { - time::Duration::from_millis(self.debug.initial_connection_timeout) + pub fn get_initial_connection_timeout(&self) -> Duration { + self.debug.initial_connection_timeout + } + + pub fn get_packet_forwarding_max_reconnections(&self) -> u32 { + self.debug.maximum_reconnection_attempts + } + + pub fn get_cache_entry_ttl(&self) -> Duration { + self.debug.cache_entry_ttl + } + + pub fn get_version(&self) -> &str { + &self.mixnode.version + } + + pub fn get_incentives_address(&self) -> Option { + self.mixnode.incentives_address.clone() + } + + // upgrade-specific + pub(crate) fn set_default_identity_keypair_paths(&mut self) { + self.mixnode.private_identity_key_file = + self::MixNode::default_private_identity_key_file(&self.mixnode.id); + self.mixnode.public_identity_key_file = + self::MixNode::default_public_identity_key_file(&self.mixnode.id); } } #[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] pub struct MixNode { + /// Version of the mixnode for which this configuration was created. + #[serde(default = "missing_string_value")] + version: String, + /// ID specifies the human readable ID of this particular mixnode. id: String, @@ -277,7 +395,7 @@ pub struct MixNode { /// Socket address to which this mixnode will bind to and will be listening for packets. listening_address: SocketAddr, - /// Optional address announced to the directory server for the clients to connect to. + /// Optional address announced to the validator for the clients to connect to. /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address /// later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. /// Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` @@ -285,32 +403,52 @@ pub struct MixNode { /// `listening_address`. announce_address: String, + /// Path to file containing private identity key. + #[serde(default = "missing_string_value")] + private_identity_key_file: PathBuf, + + /// Path to file containing public identity key. + #[serde(default = "missing_string_value")] + public_identity_key_file: PathBuf, + /// Path to file containing private sphinx key. private_sphinx_key_file: PathBuf, /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - // The idea of additional 'directory servers' is to let mixes report their presence - // and metrics to separate places - /// Directory server to which the server will be reporting their presence data. - presence_directory_server: String, + /// Validator server to which the node will be reporting their presence data. + #[serde(default = "missing_string_value")] + validator_rest_url: String, - /// Directory server to which the server will be reporting their metrics data. - metrics_directory_server: String, + /// Metrics server to which the node will be reporting their metrics data. + #[serde(default = "missing_string_value")] + metrics_server_url: String, /// nym_home_directory specifies absolute path to the home nym MixNodes directory. /// It is expected to use default value and hence .toml file should not redefine this field. nym_root_directory: PathBuf, + + /// Optional, if participating in the incentives program, payment address. + #[serde(deserialize_with = "deserialize_option_string", default)] + incentives_address: Option, } impl MixNode { + fn default_private_identity_key_file(id: &str) -> PathBuf { + Config::default_data_directory(id).join("private_identity.pem") + } + + fn default_public_identity_key_file(id: &str) -> PathBuf { + Config::default_data_directory(id).join("public_identity.pem") + } + fn default_private_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_sphinx.pem") + Config::default_data_directory(id).join("private_sphinx.pem") } fn default_public_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_sphinx.pem") + Config::default_data_directory(id).join("public_sphinx.pem") } fn default_location() -> String { @@ -321,6 +459,7 @@ impl MixNode { impl Default for MixNode { fn default() -> Self { MixNode { + version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), location: Self::default_location(), layer: 0, @@ -328,11 +467,14 @@ impl Default for MixNode { .parse() .unwrap(), announce_address: format!("127.0.0.1:{}", DEFAULT_LISTENING_PORT), + private_identity_key_file: Default::default(), + public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - presence_directory_server: DEFAULT_DIRECTORY_SERVER.to_string(), - metrics_directory_server: DEFAULT_DIRECTORY_SERVER.to_string(), + validator_rest_url: DEFAULT_VALIDATOR_REST_ENDPOINT.to_string(), + metrics_server_url: DEFAULT_METRICS_SERVER.to_string(), nym_root_directory: Config::default_root_directory(), + incentives_address: None, } } } @@ -350,62 +492,57 @@ impl Default for Logging { #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] pub struct Debug { - /// Delay between each subsequent presence data being sent. - /// The provided value is interpreted as milliseconds. - presence_sending_delay: u64, - - /// Delay between each subsequent metrics data being sent. - /// The provided value is interpreted as milliseconds. - metrics_sending_delay: u64, - /// Delay between each subsequent running metrics statistics being logged. - /// The provided value is interpreted as milliseconds. - metrics_running_stats_logging_delay: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + metrics_running_stats_logging_delay: Duration, /// Initial value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - /// The provided value is interpreted as milliseconds. - packet_forwarding_initial_backoff: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + packet_forwarding_initial_backoff: Duration, /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - /// The provided value is interpreted as milliseconds. - packet_forwarding_maximum_backoff: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + packet_forwarding_maximum_backoff: Duration, /// Timeout for establishing initial connection when trying to forward a sphinx packet. - /// The provider value is interpreted as milliseconds. - initial_connection_timeout: u64, + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + initial_connection_timeout: Duration, + + /// Maximum number of retries node is going to attempt to re-establish existing connection + /// to another node when forwarding sphinx packets. + maximum_reconnection_attempts: u32, + + /// Duration for which a cached vpn processing result is going to get stored for. + #[serde( + deserialize_with = "deserialize_duration", + serialize_with = "humantime_serde::serialize" + )] + cache_entry_ttl: Duration, } impl Default for Debug { fn default() -> Self { Debug { - presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, - metrics_sending_delay: DEFAULT_METRICS_SENDING_DELAY, metrics_running_stats_logging_delay: DEFAULT_METRICS_RUNNING_STATS_LOGGING_DELAY, packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, + maximum_reconnection_attempts: DEFAULT_MAXIMUM_RECONNECTION_ATTEMPTS, + cache_entry_ttl: DEFAULT_CACHE_ENTRY_TTL, } } } - -#[cfg(test)] -mod mixnode_config { - use super::*; - - #[test] - fn after_saving_default_config_the_loaded_one_is_identical() { - // need to figure out how to do something similar but without touching the disk - // or the file system at all... - let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); - let default_config = Config::default().with_id("foomp".to_string()); - default_config - .save_to_file(Some(temp_location.clone())) - .unwrap(); - - let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); - - assert_eq!(default_config, loaded_config); - } -} diff --git a/mixnode/src/config/persistence/pathfinder.rs b/mixnode/src/config/persistence/pathfinder.rs index 3d944c952f..ba9214b7a8 100644 --- a/mixnode/src/config/persistence/pathfinder.rs +++ b/mixnode/src/config/persistence/pathfinder.rs @@ -17,7 +17,8 @@ use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct MixNodePathfinder { - config_dir: PathBuf, + identity_private_key: PathBuf, + identity_public_key: PathBuf, private_sphinx_key: PathBuf, public_sphinx_key: PathBuf, } @@ -25,12 +26,21 @@ pub struct MixNodePathfinder { impl MixNodePathfinder { pub fn new_from_config(config: &Config) -> Self { MixNodePathfinder { - config_dir: config.get_config_file_save_location(), + identity_private_key: config.get_private_identity_key_file(), + identity_public_key: config.get_public_identity_key_file(), private_sphinx_key: config.get_private_sphinx_key_file(), public_sphinx_key: config.get_public_sphinx_key_file(), } } + pub fn private_identity_key(&self) -> &Path { + &self.identity_private_key + } + + pub fn public_identity_key(&self) -> &Path { + &self.identity_public_key + } + pub fn private_encryption_key(&self) -> &Path { &self.private_sphinx_key } diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index 3dbce83b0b..15b480a169 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -24,6 +24,9 @@ pub(crate) fn config_template() -> &'static str { ##### main base mixnode config options ##### [mixnode] +# Version of the mixnode for which this configuration was created. +version = '{{ mixnode.version }}' + # Human readable ID of this particular mixnode. id = '{{ mixnode.id }}' @@ -39,12 +42,21 @@ layer = {{ mixnode.layer }} # Socket address to which this mixnode will bind to and will be listening for packets. listening_address = '{{ mixnode.listening_address }}' +# Path to file containing private identity key. +private_identity_key_file = '{{ mixnode.private_identity_key_file }}' + +# Path to file containing public identity key. +public_identity_key_file = '{{ mixnode.public_identity_key_file }}' + # Path to file containing private identity key. private_sphinx_key_file = '{{ mixnode.private_sphinx_key_file }}' # Path to file containing public sphinx key. public_sphinx_key_file = '{{ mixnode.public_sphinx_key_file }}' +# Optional, if participating in the incentives program, payment address. +incentives_address = '{{ mixnode.incentives_address }}' + ##### additional mixnode config options ##### # Optional address announced to the directory server for the clients to connect to. @@ -55,11 +67,11 @@ public_sphinx_key_file = '{{ mixnode.public_sphinx_key_file }}' # `listening_address`. announce_address = '{{ mixnode.announce_address }}' -# Directory server to which the server will be reporting their presence data. -presence_directory_server = '{{ mixnode.presence_directory_server }}' +# Validator server to which the node will be reporting their presence data. +validator_rest_url = '{{ mixnode.validator_rest_url }}' -# Directory server to which the server will be reporting their metrics data. -metrics_directory_server = '{{ mixnode.metrics_directory_server }}' +# Metrics server to which the node will be reporting their metrics data. +metrics_server_url = '{{ mixnode.metrics_server_url }}' ##### advanced configuration options ##### diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index 1907aaa641..31373f6fe7 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -14,7 +14,6 @@ use clap::{App, ArgMatches}; -pub mod built_info; mod commands; mod config; mod node; @@ -25,11 +24,12 @@ fn main() { println!("{}", banner()); let arg_matches = App::new("Nym Mixnode") - .version(built_info::PKG_VERSION) + .version(env!("CARGO_PKG_VERSION")) .author("Nymtech") .about("Implementation of the Loopix-based Mixnode") .subcommand(commands::init::command_args()) .subcommand(commands::run::command_args()) + .subcommand(commands::upgrade::command_args()) .get_matches(); execute(arg_matches); @@ -39,6 +39,7 @@ fn execute(matches: ArgMatches) { match matches.subcommand() { ("init", Some(m)) => commands::init::execute(m), ("run", Some(m)) => commands::run::execute(m), + ("upgrade", Some(m)) => commands::upgrade::execute(m), _ => println!("{}", usage()), } } @@ -60,7 +61,7 @@ fn banner() -> String { (mixnode - version {:}) "#, - built_info::PKG_VERSION + env!("CARGO_PKG_VERSION") ) } diff --git a/mixnode/src/node/listener.rs b/mixnode/src/node/listener.rs deleted file mode 100644 index 7fec60304e..0000000000 --- a/mixnode/src/node/listener.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::node::packet_processing::{MixProcessingResult, PacketProcessor}; -use futures::channel::mpsc; -use log::*; -use nymsphinx::framing::SphinxCodec; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::io; -use std::net::SocketAddr; -use tokio::runtime::Handle; -use tokio::stream::StreamExt; -use tokio::task::JoinHandle; -use tokio_util::codec::Framed; - -async fn process_received_packet( - sphinx_packet: SphinxPacket, - packet_processor: PacketProcessor, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, -) { - // all processing incl. delay was done, the only thing left is to forward it - match packet_processor.process_sphinx_packet(sphinx_packet).await { - Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), - Ok(res) => match res { - MixProcessingResult::ForwardHop(hop_address, forward_packet) => { - // send our data to tcp client for forwarding. If forwarding fails, then it fails, - // it's not like we can do anything about it - // - // in unbounded_send() failed it means that the receiver channel was disconnected - // and hence something weird must have happened without a way of recovering - forwarding_channel - .unbounded_send((hop_address, forward_packet)) - .unwrap(); - packet_processor.report_sent(hop_address); - } - MixProcessingResult::LoopMessage => { - warn!("Somehow processed a loop cover message that we haven't implemented yet!") - } - }, - } -} - -async fn process_socket_connection( - socket: tokio::net::TcpStream, - packet_processor: PacketProcessor, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, -) { - let mut framed = Framed::new(socket, SphinxCodec); - while let Some(sphinx_packet) = framed.next().await { - match sphinx_packet { - Ok(sphinx_packet) => { - // we *really* need a worker pool here, because if we receive too many packets, - // we will spawn too many tasks and starve CPU due to context switching. - // (because presumably tokio has some concept of context switching in its - // scheduler) - tokio::spawn(process_received_packet( - sphinx_packet, - packet_processor.clone(), - forwarding_channel.clone(), - )); - } - Err(err) => { - error!( - "The socket connection got corrupted with error: {:?}. Closing the socket", - err - ); - return; - } - } - } - info!( - "Closing connection from {:?}", - framed.into_inner().peer_addr() - ); -} - -pub(crate) fn run_socket_listener( - handle: &Handle, - addr: SocketAddr, - packet_processor: PacketProcessor, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, -) -> JoinHandle> { - let handle_clone = handle.clone(); - handle.spawn(async move { - let mut listener = tokio::net::TcpListener::bind(addr).await?; - loop { - let (socket, _) = listener.accept().await?; - - let thread_packet_processor = packet_processor.clone(); - let forwarding_channel_clone = forwarding_channel.clone(); - handle_clone.spawn(async move { - process_socket_connection( - socket, - thread_packet_processor, - forwarding_channel_clone, - ) - .await; - }); - } - }) -} diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs new file mode 100644 index 0000000000..6f931f52bc --- /dev/null +++ b/mixnode/src/node/listener/connection_handler/mod.rs @@ -0,0 +1,123 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::node::listener::connection_handler::packet_processing::{ + MixProcessingResult, PacketProcessor, +}; +use log::*; +use mixnet_client::forwarder::MixForwardingSender; +use nymsphinx::forwarding::packet::MixPacket; +use nymsphinx::framing::codec::SphinxCodec; +use nymsphinx::framing::packet::FramedSphinxPacket; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::net::TcpStream; +use tokio::stream::StreamExt; +use tokio_util::codec::Framed; + +pub(crate) mod packet_processing; + +pub(crate) struct ConnectionHandler { + packet_processor: PacketProcessor, + forwarding_channel: MixForwardingSender, +} + +impl ConnectionHandler { + pub(crate) fn new( + packet_processor: PacketProcessor, + forwarding_channel: MixForwardingSender, + ) -> Self { + ConnectionHandler { + packet_processor, + forwarding_channel, + } + } + + pub(crate) fn clone_without_cache(&self) -> Self { + ConnectionHandler { + packet_processor: self.packet_processor.clone_without_cache(), + forwarding_channel: self.forwarding_channel.clone(), + } + } + + fn forward_packet(&self, mix_packet: MixPacket) { + let routing_address = mix_packet.next_hop(); + // send our data to tcp client for forwarding. If forwarding fails, then it fails, + // it's not like we can do anything about it + // + // in unbounded_send() failed it means that the receiver channel was disconnected + // and hence something weird must have happened without a way of recovering + self.forwarding_channel.unbounded_send(mix_packet).unwrap(); + self.packet_processor.report_sent(routing_address); + } + + async fn handle_received_packet(self: Arc, framed_sphinx_packet: FramedSphinxPacket) { + // + // TODO: here be replay attack detection - it will require similar key cache to the one in + // packet processor for vpn packets, + // question: can it also be per connection vs global? + // + + // all processing including delaying, key caching, etc. was done, the only thing left is to forward it + match self + .packet_processor + .process_received(framed_sphinx_packet) + .await + { + Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), + Ok(res) => match res { + MixProcessingResult::ForwardHop(forward_packet) => { + self.forward_packet(forward_packet) + } + MixProcessingResult::FinalHop(..) => { + warn!("Somehow processed a loop cover message that we haven't implemented yet!") + } + }, + } + } + + pub(crate) async fn handle_connection(self, conn: TcpStream, remote: SocketAddr) { + debug!("Starting connection handler for {:?}", remote); + let this = Arc::new(self); + let mut framed_conn = Framed::new(conn, SphinxCodec); + while let Some(framed_sphinx_packet) = framed_conn.next().await { + match framed_sphinx_packet { + Ok(framed_sphinx_packet) => { + // TODO: benchmark spawning tokio task with full processing vs just processing it + // synchronously (without delaying inside of course, + // delay could be moved to a per-connection DelayQueue. The delay queue future + // could automatically just forward packet that is done being delayed) + // under higher load in single and multi-threaded situation. + // + // My gut feeling is saying that we might get some nice performance boost + // if we introduced the change + let this = Arc::clone(&this); + tokio::spawn(this.handle_received_packet(framed_sphinx_packet)); + } + Err(err) => { + error!( + "The socket connection got corrupted with error: {:?}. Closing the socket", + err + ); + return; + } + } + } + + info!( + "Closing connection from {:?}", + framed_conn.into_inner().peer_addr() + ); + } +} diff --git a/mixnode/src/node/listener/connection_handler/packet_processing.rs b/mixnode/src/node/listener/connection_handler/packet_processing.rs new file mode 100644 index 0000000000..ab006a3b35 --- /dev/null +++ b/mixnode/src/node/listener/connection_handler/packet_processing.rs @@ -0,0 +1,60 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::node::metrics; +use crypto::asymmetric::encryption; +use mixnode_common::cached_packet_processor::error::MixProcessingError; +use mixnode_common::cached_packet_processor::processor::CachedPacketProcessor; +pub use mixnode_common::cached_packet_processor::processor::MixProcessingResult; +use nymsphinx::addressing::nodes::NymNodeRoutingAddress; +use nymsphinx::framing::packet::FramedSphinxPacket; +use tokio::time::Duration; + +// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets +pub struct PacketProcessor { + inner_processor: CachedPacketProcessor, + metrics_reporter: metrics::MetricsReporter, +} + +impl PacketProcessor { + pub(crate) fn new( + encryption_key: &encryption::PrivateKey, + metrics_reporter: metrics::MetricsReporter, + cache_entry_ttl: Duration, + ) -> Self { + PacketProcessor { + inner_processor: CachedPacketProcessor::new(encryption_key.into(), cache_entry_ttl), + metrics_reporter, + } + } + + pub(crate) fn clone_without_cache(&self) -> Self { + PacketProcessor { + inner_processor: self.inner_processor.clone_without_cache(), + metrics_reporter: self.metrics_reporter.clone(), + } + } + + pub(crate) fn report_sent(&self, address: NymNodeRoutingAddress) { + self.metrics_reporter.report_sent(address.to_string()) + } + + pub(crate) async fn process_received( + &self, + received: FramedSphinxPacket, + ) -> Result { + self.metrics_reporter.report_received(); + self.inner_processor.process_received(received).await + } +} diff --git a/mixnode/src/node/listener/mod.rs b/mixnode/src/node/listener/mod.rs new file mode 100644 index 0000000000..cc3ca62a55 --- /dev/null +++ b/mixnode/src/node/listener/mod.rs @@ -0,0 +1,52 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::node::listener::connection_handler::ConnectionHandler; +use log::*; +use std::net::SocketAddr; +use tokio::net::TcpListener; +use tokio::task::JoinHandle; + +pub(crate) mod connection_handler; + +pub(crate) struct Listener { + address: SocketAddr, +} + +impl Listener { + pub(crate) fn new(address: SocketAddr) -> Self { + Listener { address } + } + + async fn run(&mut self, connection_handler: ConnectionHandler) { + let mut listener = TcpListener::bind(self.address) + .await + .expect("Failed to create TCP listener"); + loop { + match listener.accept().await { + Ok((socket, remote_addr)) => { + let handler = connection_handler.clone_without_cache(); + tokio::spawn(handler.handle_connection(socket, remote_addr)); + } + Err(err) => warn!("Failed to accept incoming connection - {:?}", err), + } + } + } + + pub(crate) fn start(mut self, connection_handler: ConnectionHandler) -> JoinHandle<()> { + info!("Running mix listener on {:?}", self.address.to_string()); + + tokio::spawn(async move { self.run(connection_handler).await }) + } +} diff --git a/mixnode/src/node/metrics.rs b/mixnode/src/node/metrics.rs index 1ce9315703..8392966d17 100644 --- a/mixnode/src/node/metrics.rs +++ b/mixnode/src/node/metrics.rs @@ -12,18 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use directory_client::metrics::MixMetric; -use directory_client::DirectoryClient; use futures::channel::mpsc; use futures::lock::Mutex; use futures::StreamExt; use log::*; +use metrics_client::models::metrics::MixMetric; use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, SystemTime}; -use tokio::runtime::Handle; use tokio::task::JoinHandle; +const METRICS_FAILURE_BACKOFF: Duration = Duration::from_secs(30); + type SentMetricsMap = HashMap; pub(crate) enum MetricEvent { @@ -88,8 +88,8 @@ impl MetricsReceiver { } } - fn start(mut self, handle: &Handle) -> JoinHandle<()> { - handle.spawn(async move { + fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { while let Some(metrics_data) = self.metrics_rx.next().await { match metrics_data { MetricEvent::Received => self.metrics.increment_received_metrics().await, @@ -104,44 +104,41 @@ impl MetricsReceiver { struct MetricsSender { metrics: MixMetrics, - directory_client: directory_client::Client, + metrics_client: metrics_client::Client, pub_key_str: String, - sending_delay: Duration, metrics_informer: MetricsInformer, } impl MetricsSender { fn new( metrics: MixMetrics, - directory_server: String, + metrics_server: String, pub_key_str: String, - sending_delay: Duration, running_logging_delay: Duration, ) -> Self { MetricsSender { metrics, - directory_client: directory_client::Client::new(directory_client::Config::new( - directory_server, + metrics_client: metrics_client::Client::new(metrics_client::Config::new( + metrics_server, )), pub_key_str, - sending_delay, metrics_informer: MetricsInformer::new(running_logging_delay), } } - fn start(mut self, handle: &Handle) -> JoinHandle<()> { - handle.spawn(async move { + fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { loop { - // set the deadline in the future - let sending_delay = tokio::time::delay_for(self.sending_delay); + // // set the deadline in the future + // let sending_delay = tokio::time::delay_for(self.sending_delay); let (received, sent) = self.metrics.acquire_and_reset_metrics().await; self.metrics_informer.update_running_stats(received, &sent); self.metrics_informer.log_report_stats(received, &sent); self.metrics_informer.try_log_running_stats(); - match self - .directory_client + let sending_delay = match self + .metrics_client .post_mix_metrics(MixMetric { pub_key: self.pub_key_str.clone(), received, @@ -149,9 +146,15 @@ impl MetricsSender { }) .await { - Err(err) => error!("failed to send metrics - {:?}", err), - Ok(_) => debug!("sent metrics information"), - } + Err(err) => { + error!("failed to send metrics - {:?}", err); + tokio::time::delay_for(METRICS_FAILURE_BACKOFF) + } + Ok(new_interval) => { + debug!("sent metrics information"); + tokio::time::delay_for(Duration::from_secs(new_interval.next_report_in)) + } + }; // wait for however much is left sending_delay.await; @@ -263,7 +266,6 @@ impl MetricsController { pub(crate) fn new( directory_server: String, pub_key_str: String, - sending_delay: Duration, running_stats_logging_delay: Duration, ) -> Self { let (metrics_tx, metrics_rx) = mpsc::unbounded(); @@ -274,7 +276,6 @@ impl MetricsController { shared_metrics.clone(), directory_server, pub_key_str, - sending_delay, running_stats_logging_delay, ), receiver: MetricsReceiver::new(shared_metrics, metrics_rx), @@ -283,10 +284,10 @@ impl MetricsController { } // reporter is how node is going to be accessing the metrics data - pub(crate) fn start(self, handle: &Handle) -> MetricsReporter { + pub(crate) fn start(self) -> MetricsReporter { // TODO: should we do anything with JoinHandle(s) returned by start methods? - self.receiver.start(handle); - self.sender.start(handle); + self.receiver.start(); + self.sender.start(); self.reporter } } diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index ae7e5e1d05..eeea18553c 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -13,135 +13,158 @@ // limitations under the License. use crate::config::Config; -use crate::node::packet_processing::PacketProcessor; -use crypto::asymmetric::encryption; -use directory_client::DirectoryClient; -use futures::channel::mpsc; +use crate::node::listener::connection_handler::packet_processing::PacketProcessor; +use crate::node::listener::connection_handler::ConnectionHandler; +use crate::node::listener::Listener; +use crypto::asymmetric::{encryption, identity}; use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; +use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; +use std::sync::Arc; use tokio::runtime::Runtime; mod listener; mod metrics; -mod packet_forwarding; -pub(crate) mod packet_processing; mod presence; // the MixNode will live for whole duration of this program pub struct MixNode { - runtime: Runtime, config: Config, - sphinx_keypair: encryption::KeyPair, + identity_keypair: Arc, + sphinx_keypair: Arc, } impl MixNode { - pub fn new(config: Config, sphinx_keypair: encryption::KeyPair) -> Self { + pub fn new( + config: Config, + identity_keypair: identity::KeyPair, + sphinx_keypair: encryption::KeyPair, + ) -> Self { MixNode { - runtime: Runtime::new().unwrap(), config, - sphinx_keypair, + identity_keypair: Arc::new(identity_keypair), + sphinx_keypair: Arc::new(sphinx_keypair), } } - fn start_presence_notifier(&self) { - info!("Starting presence notifier..."); - let notifier_config = presence::NotifierConfig::new( - self.config.get_location(), - self.config.get_presence_directory_server(), - self.config.get_announce_address(), - self.sphinx_keypair.public_key().to_base58_string(), - self.config.get_layer(), - self.config.get_presence_sending_delay(), - ); - presence::Notifier::new(notifier_config).start(self.runtime.handle()); - } - fn start_metrics_reporter(&self) -> metrics::MetricsReporter { info!("Starting metrics reporter..."); metrics::MetricsController::new( - self.config.get_metrics_directory_server(), - self.sphinx_keypair.public_key().to_base58_string(), - self.config.get_metrics_sending_delay(), + self.config.get_metrics_server(), + self.identity_keypair.public_key().to_base58_string(), self.config.get_metrics_running_stats_logging_delay(), ) - .start(self.runtime.handle()) + .start() } fn start_socket_listener( &self, metrics_reporter: metrics::MetricsReporter, - forwarding_channel: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, + forwarding_channel: MixForwardingSender, ) { info!("Starting socket listener..."); - // this is the only location where our private key is going to be copied - // it will be held in memory owned by `MixNode` and inside an Arc of `PacketProcessor` - let packet_processor = - PacketProcessor::new(self.sphinx_keypair.private_key().clone(), metrics_reporter); - listener::run_socket_listener( - self.runtime.handle(), - self.config.get_listening_address(), - packet_processor, - forwarding_channel, + let packet_processor = PacketProcessor::new( + self.sphinx_keypair.private_key(), + metrics_reporter, + self.config.get_cache_entry_ttl(), ); + + let connection_handler = ConnectionHandler::new(packet_processor, forwarding_channel); + + let listener = Listener::new(self.config.get_listening_address()); + + listener.start(connection_handler); } - fn start_packet_forwarder( - &mut self, - ) -> mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)> { + fn start_packet_forwarder(&mut self) -> MixForwardingSender { info!("Starting packet forwarder..."); - self.runtime - .enter(|| { - packet_forwarding::PacketForwarder::new( - self.config.get_packet_forwarding_initial_backoff(), - self.config.get_packet_forwarding_maximum_backoff(), - self.config.get_initial_connection_timeout(), - ) - }) - .start(self.runtime.handle()) + + let (mut packet_forwarder, packet_sender) = PacketForwarder::new( + self.config.get_packet_forwarding_initial_backoff(), + self.config.get_packet_forwarding_maximum_backoff(), + self.config.get_initial_connection_timeout(), + self.config.get_packet_forwarding_max_reconnections(), + ); + + tokio::spawn(async move { packet_forwarder.run().await }); + packet_sender } - fn check_if_same_ip_node_exists(&mut self) -> Option { - let directory_client_config = - directory_client::Config::new(self.config.get_presence_directory_server()); - let directory_client = directory_client::Client::new(directory_client_config); - let topology = self - .runtime - .block_on(directory_client.get_topology()) - .ok()?; + async fn check_if_same_ip_node_exists(&mut self) -> Option { + let validator_client_config = + validator_client::Config::new(self.config.get_validator_rest_endpoint()); + let validator_client = validator_client::Client::new(validator_client_config); + let topology = validator_client + .get_topology() + .await + .expect("failed to grab network topology"); let existing_mixes_presence = topology.mix_nodes; existing_mixes_presence .iter() - .find(|node| node.host == self.config.get_announce_address()) - .map(|node| node.pub_key.clone()) + .find(|node| node.mix_host() == self.config.get_announce_address()) + .map(|node| node.identity()) } - pub fn run(&mut self) { - info!("Starting nym mixnode"); - - if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists() { - error!( - "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", - duplicate_node_key - ); - return; - } - let forwarding_channel = self.start_packet_forwarder(); - let metrics_reporter = self.start_metrics_reporter(); - self.start_socket_listener(metrics_reporter, forwarding_channel); - self.start_presence_notifier(); - - info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!"); - - if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) { + async fn wait_for_interrupt(&self) { + if let Err(e) = tokio::signal::ctrl_c().await { error!( "There was an error while capturing SIGINT - {:?}. We will terminate regardless", e ); } - println!( "Received SIGINT - the mixnode will terminate now (threads are not YET nicely stopped)" ); + info!("Trying to unregister with the validator..."); + if let Err(err) = presence::unregister_with_validator( + self.config.get_validator_rest_endpoint(), + self.identity_keypair.public_key().to_base58_string(), + ) + .await + { + error!("failed to unregister with validator... - {:?}", err) + } + } + + pub fn run(&mut self) { + info!("Starting nym mixnode"); + + let mut runtime = Runtime::new().unwrap(); + + runtime.block_on(async { + if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists().await { + if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() { + warn!("We seem to have not unregistered after going offline - there's a node with identical identity and announce-host as us registered.") + } else { + error!( + "Our announce-host is identical to an existing node's announce-host! (its key is {:?}", + duplicate_node_key + ); + return; + } + } + + if let Err(err) = presence::register_with_validator( + self.config.get_validator_rest_endpoint(), + self.config.get_announce_address(), + self.identity_keypair.public_key().to_base58_string(), + self.sphinx_keypair.public_key().to_base58_string(), + self.config.get_version().to_string(), + self.config.get_location(), + self.config.get_layer(), + self.config.get_incentives_address(), + ).await { + error!("failed to register with the validator - {:?}", err); + return + } + + let forwarding_channel = self.start_packet_forwarder(); + let metrics_reporter = self.start_metrics_reporter(); + self.start_socket_listener(metrics_reporter, forwarding_channel); + + info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!"); + + self.wait_for_interrupt().await + }) } } diff --git a/mixnode/src/node/packet_forwarding.rs b/mixnode/src/node/packet_forwarding.rs deleted file mode 100644 index 29a112d442..0000000000 --- a/mixnode/src/node/packet_forwarding.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use futures::channel::mpsc; -use futures::StreamExt; -use log::*; -use nymsphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket}; -use std::time::Duration; -use tokio::runtime::Handle; - -pub(crate) struct PacketForwarder { - tcp_client: mixnet_client::Client, - conn_tx: mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)>, - conn_rx: mpsc::UnboundedReceiver<(NymNodeRoutingAddress, SphinxPacket)>, -} - -impl PacketForwarder { - pub(crate) fn new( - initial_reconnection_backoff: Duration, - maximum_reconnection_backoff: Duration, - initial_connection_timeout: Duration, - ) -> PacketForwarder { - let tcp_client_config = mixnet_client::Config::new( - initial_reconnection_backoff, - maximum_reconnection_backoff, - initial_connection_timeout, - ); - - let (conn_tx, conn_rx) = mpsc::unbounded(); - - PacketForwarder { - tcp_client: mixnet_client::Client::new(tcp_client_config), - conn_tx, - conn_rx, - } - } - - pub(crate) fn start( - mut self, - handle: &Handle, - ) -> mpsc::UnboundedSender<(NymNodeRoutingAddress, SphinxPacket)> { - // TODO: what to do with the lost JoinHandle? - let sender_channel = self.conn_tx.clone(); - handle.spawn(async move { - while let Some((address, packet)) = self.conn_rx.next().await { - trace!("Going to forward packet to {:?}", address); - // as a mix node we don't care about responses, we just want to fire packets - // as quickly as possible - self.tcp_client.send(address, packet, false).await.unwrap(); // if we're not waiting for response, we MUST get an Ok - } - }); - sender_channel - } -} diff --git a/mixnode/src/node/packet_processing.rs b/mixnode/src/node/packet_processing.rs deleted file mode 100644 index f075648218..0000000000 --- a/mixnode/src/node/packet_processing.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::node::metrics; -use crypto::asymmetric::encryption; -use log::*; -use nymsphinx::addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; -use nymsphinx::{ - Delay as SphinxDelay, Error as SphinxError, NodeAddressBytes, ProcessedPacket, SphinxPacket, -}; -use std::convert::TryFrom; -use std::sync::Arc; - -#[derive(Debug)] -pub enum MixProcessingError { - ReceivedFinalHopError, - SphinxProcessingError(SphinxError), - InvalidHopAddress, -} - -pub enum MixProcessingResult { - ForwardHop(NymNodeRoutingAddress, SphinxPacket), - #[allow(dead_code)] - LoopMessage, -} - -impl From for MixProcessingError { - // for time being just have a single error instance for all possible results of SphinxError - fn from(err: SphinxError) -> Self { - use MixProcessingError::*; - - SphinxProcessingError(err) - } -} - -impl From for MixProcessingError { - fn from(_: NymNodeRoutingAddressError) -> Self { - use MixProcessingError::*; - - InvalidHopAddress - } -} - -// PacketProcessor contains all data required to correctly unwrap and forward sphinx packets -#[derive(Clone)] -pub struct PacketProcessor { - secret_key: Arc, - metrics_reporter: metrics::MetricsReporter, -} - -impl PacketProcessor { - pub(crate) fn new( - secret_key: encryption::PrivateKey, - metrics_reporter: metrics::MetricsReporter, - ) -> Self { - PacketProcessor { - secret_key: Arc::new(secret_key), - metrics_reporter, - } - } - - pub(crate) fn report_sent(&self, addr: NymNodeRoutingAddress) { - self.metrics_reporter.report_sent(addr.to_string()) - } - - async fn process_forward_hop( - &self, - packet: SphinxPacket, - forward_address: NodeAddressBytes, - delay: SphinxDelay, - ) -> Result { - let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?; - - // Delay packet for as long as required - tokio::time::delay_for(delay.to_duration()).await; - - Ok(MixProcessingResult::ForwardHop(next_hop_address, packet)) - } - - pub(crate) async fn process_sphinx_packet( - &self, - packet: SphinxPacket, - ) -> Result { - // we received something resembling a sphinx packet, report it! - self.metrics_reporter.report_received(); - match packet.process(&self.secret_key.as_ref().into()) { - Ok(ProcessedPacket::ProcessedPacketForwardHop(packet, address, delay)) => { - self.process_forward_hop(packet, address, delay).await - } - Ok(ProcessedPacket::ProcessedPacketFinalHop(_, _, _)) => { - warn!("Received a loop cover message that we haven't implemented yet!"); - Err(MixProcessingError::ReceivedFinalHopError) - } - Err(e) => { - warn!("Failed to unwrap Sphinx packet: {:?}", e); - Err(MixProcessingError::SphinxProcessingError(e)) - } - } - } -} - -// TODO: the test that definitely needs to be written is as follows: -// we are stuck trying to write to mix A, can we still forward just fine to mix B? diff --git a/mixnode/src/node/presence.rs b/mixnode/src/node/presence.rs index e31abd06ab..a7d0f62eb8 100644 --- a/mixnode/src/node/presence.rs +++ b/mixnode/src/node/presence.rs @@ -12,90 +12,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::built_info; -use directory_client::presence::mixnodes::MixNodePresence; -use directory_client::DirectoryClient; -use log::{error, trace}; -use std::time::Duration; -use tokio::runtime::Handle; -use tokio::task::JoinHandle; +use validator_client::models::mixnode::MixRegistrationInfo; +use validator_client::ValidatorClientError; -pub(crate) struct NotifierConfig { +// there's no point in keeping the validator client persistently as it might be literally hours or days +// before it's used again +pub(crate) async fn register_with_validator( + validator_endpoint: String, + mix_host: String, + identity_key: String, + sphinx_key: String, + version: String, location: String, - directory_server: String, - announce_host: String, - pub_key_string: String, layer: u64, - sending_delay: Duration, + incentives_address: Option, +) -> Result<(), ValidatorClientError> { + let config = validator_client::Config::new(validator_endpoint); + let validator_client = validator_client::Client::new(config); + + let registration_info = MixRegistrationInfo::new( + mix_host, + identity_key, + sphinx_key, + version, + location, + layer, + incentives_address, + ); + + validator_client.register_mix(registration_info).await } -impl NotifierConfig { - pub(crate) fn new( - location: String, - directory_server: String, - announce_host: String, - pub_key_string: String, - layer: u64, - sending_delay: Duration, - ) -> Self { - NotifierConfig { - location, - directory_server, - announce_host, - pub_key_string, - layer, - sending_delay, - } - } -} - -pub(crate) struct Notifier { - net_client: directory_client::Client, - presence: MixNodePresence, - sending_delay: Duration, -} - -impl Notifier { - pub(crate) fn new(config: NotifierConfig) -> Notifier { - let directory_client_cfg = directory_client::Config { - base_url: config.directory_server, - }; - let net_client = directory_client::Client::new(directory_client_cfg); - let presence = MixNodePresence { - location: config.location, - host: config.announce_host, - pub_key: config.pub_key_string, - layer: config.layer, - last_seen: 0, - version: built_info::PKG_VERSION.to_string(), - }; - Notifier { - net_client, - presence, - sending_delay: config.sending_delay, - } - } - - async fn notify(&self) { - match self - .net_client - .post_mixnode_presence(self.presence.clone()) - .await - { - Err(err) => error!("failed to send presence - {:?}", err), - Ok(_) => trace!("sent presence information"), - } - } - - pub fn start(self, handle: &Handle) -> JoinHandle<()> { - handle.spawn(async move { - loop { - // set the deadline in the future - let sending_delay = tokio::time::delay_for(self.sending_delay); - self.notify().await; - // wait for however much is left - sending_delay.await; - } - }) - } +pub(crate) async fn unregister_with_validator( + validator_endpoint: String, + identity_key: String, +) -> Result<(), ValidatorClientError> { + let config = validator_client::Config::new(validator_endpoint); + let validator_client = validator_client::Client::new(config); + + validator_client.unregister_node(&identity_key).await } diff --git a/network-monitor/Cargo.toml b/network-monitor/Cargo.toml new file mode 100644 index 0000000000..8b162cc404 --- /dev/null +++ b/network-monitor/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "nym-network-monitor" +version = "0.9.0" +authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "2.33.0" +dotenv = "0.15.0" +futures = "0.3" +log = "0.4" +pretty_env_logger = "0.3" +rand = "0.7" +serde = "1.0" +serde_json = "1.0" +tokio = { version = "0.2", features = ["signal", "rt-threaded", "macros"] } + +## internal +crypto = { path = "../common/crypto" } +gateway-client = { path = "../common/client-libs/gateway-client" } +nymsphinx = { path = "../common/nymsphinx" } +topology = { path = "../common/topology" } +validator-client = { path = "../common/client-libs/validator-client" } +version-checker = { path = "../common/version-checker" } + +[dev-dependencies] diff --git a/network-monitor/src/chunker.rs b/network-monitor/src/chunker.rs new file mode 100644 index 0000000000..3a22a893b7 --- /dev/null +++ b/network-monitor/src/chunker.rs @@ -0,0 +1,74 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{DefRng, DEFAULT_RNG}; +use nymsphinx::forwarding::packet::MixPacket; +use nymsphinx::params::PacketMode; +use nymsphinx::{ + acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer, +}; +use std::time::Duration; +use topology::NymTopology; + +const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200); +const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200); + +pub(crate) struct Chunker { + rng: DefRng, + me: Recipient, + message_preparer: MessagePreparer, +} + +impl Chunker { + pub(crate) fn new(me: Recipient) -> Self { + Chunker { + rng: DEFAULT_RNG, + me, + message_preparer: MessagePreparer::new( + DEFAULT_RNG, + me, + DEFAULT_AVERAGE_PACKET_DELAY, + DEFAULT_AVERAGE_ACK_DELAY, + PacketMode::Mix, + None, + ), + } + } + + pub(crate) async fn prepare_messages( + &mut self, + message: Vec, + topology: &NymTopology, + ) -> Vec { + let ack_key: AckKey = AckKey::new(&mut self.rng); + + let (split_message, _reply_keys) = self + .message_preparer + .prepare_and_split_message(message, false, &topology) + .expect("failed to split the message"); + + let mut mix_packets = Vec::with_capacity(split_message.len()); + for message_chunk in split_message { + // don't bother with acks etc. for time being + let prepared_fragment = self + .message_preparer + .prepare_chunk_for_sending(message_chunk, &topology, &ack_key, &self.me) + .await + .unwrap(); + + mix_packets.push(prepared_fragment.mix_packet); + } + mix_packets + } +} diff --git a/network-monitor/src/main.rs b/network-monitor/src/main.rs new file mode 100644 index 0000000000..9bc6dc0be1 --- /dev/null +++ b/network-monitor/src/main.rs @@ -0,0 +1,277 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::monitor::MixnetReceiver; +use crate::run_info::{TestRunUpdateReceiver, TestRunUpdateSender}; +use crate::tested_network::good_topology::parse_topology_file; +use crate::tested_network::TestedNetwork; +use clap::{App, Arg, ArgMatches}; +use crypto::asymmetric::{encryption, identity}; +use futures::channel::mpsc; +use gateway_client::GatewayClient; +use log::*; +use monitor::{AckSender, MixnetSender, Monitor}; +use notifications::Notifier; +use nymsphinx::addressing::clients::Recipient; +use packet_sender::PacketSender; +use rand::rngs::OsRng; +use std::sync::Arc; +use std::time; +use topology::{gateway, NymTopology}; + +mod chunker; +mod monitor; +mod notifications; +mod packet_sender; +mod run_info; +mod test_packet; +mod tested_network; + +pub(crate) type DefRng = OsRng; +pub(crate) const DEFAULT_RNG: DefRng = OsRng; + +const V4_TOPOLOGY_ARG: &str = "v4-topology-filepath"; +const V6_TOPOLOGY_ARG: &str = "v6-topology-filepath"; +const VALIDATOR_ARG: &str = "validator"; +const DETAILED_REPORT_ARG: &str = "detailed-report"; + +fn parse_args<'a>() -> ArgMatches<'a> { + App::new("Nym Network Monitor") + .author("Nymtech") + .arg( + Arg::with_name(V4_TOPOLOGY_ARG) + .help("location of .json file containing IPv4 'good' network topology") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name(V6_TOPOLOGY_ARG) + .help("location of .json file containing IPv6 'good' network topology") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name(VALIDATOR_ARG) + .help("REST endpoint of the validator the monitor will grab nodes to test") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name(DETAILED_REPORT_ARG) + .help("specifies whether a detailed report should be printed after each run"), + ) + .get_matches() +} + +#[tokio::main] +async fn main() { + println!("Network monitor starting..."); + dotenv::dotenv().ok(); + let matches = parse_args(); + let v4_topology_path = matches.value_of(V4_TOPOLOGY_ARG).unwrap(); + let v6_topology_path = matches.value_of(V6_TOPOLOGY_ARG).unwrap(); + + let v4_topology = parse_topology_file(v4_topology_path); + let v6_topology = parse_topology_file(v6_topology_path); + + let validator_rest_uri = matches.value_of(VALIDATOR_ARG).unwrap(); + let detailed_report = matches.is_present(DETAILED_REPORT_ARG); + + check_if_up_to_date(&v4_topology, &v6_topology); + setup_logging(); + + println!("* validator server: {}", validator_rest_uri); + + // TODO: THIS MUST BE UPDATED!! + // TODO: THIS MUST BE UPDATED!! + // TODO: THIS MUST BE UPDATED!! + warn!("using v4 gateway for both topologies!"); + let gateway = v4_topology.gateways()[0].clone(); + + // TODO: this might change if it turns out we need both v4 and v6 gateway clients + println!("* gateway: {}", gateway.identity_key.to_base58_string()); + + // Channels for task communication + let (ack_sender, _ack_receiver) = mpsc::unbounded(); + let (mixnet_sender, mixnet_receiver) = mpsc::unbounded(); + let (test_run_sender, test_run_receiver) = mpsc::unbounded(); + + // Generate a new set of identity keys. These are ephemeral, and change on each run. + // JS: do they? or rather should they? + let identity_keypair = identity::KeyPair::new(); + let encryption_keypair = encryption::KeyPair::new(); + + // We need our own address as a Recipient so we can send ourselves test packets + let self_address = Recipient::new( + *identity_keypair.public_key(), + *encryption_keypair.public_key(), + gateway.identity_key, + ); + + let validator_client = new_validator_client(validator_rest_uri); + + let mut network_monitor = Monitor::new(); + + let notifier = new_notifier( + encryption_keypair, + Arc::clone(&validator_client), + mixnet_receiver, + test_run_receiver, + detailed_report, + ); + + let gateway_client = new_gateway_client(gateway, identity_keypair, ack_sender, mixnet_sender); + let tested_network = new_tested_network(gateway_client, v4_topology, v6_topology).await; + + let packet_sender = new_packet_sender( + validator_client, + tested_network, + self_address, + test_run_sender, + ); + + network_monitor.run(notifier, packet_sender).await; +} + +async fn new_tested_network( + gateway_client: GatewayClient, + good_v4_topology: NymTopology, + good_v6_topology: NymTopology, +) -> TestedNetwork { + // TODO: possibly change that if it turns out we need two clients (v4 and v6) + let mut tested_network = + TestedNetwork::new_good(gateway_client, good_v4_topology, good_v6_topology); + tested_network.start_gateway_client().await; + tested_network +} + +fn new_packet_sender( + validator_client: Arc, + tested_network: TestedNetwork, + self_address: Recipient, + test_run_sender: TestRunUpdateSender, +) -> PacketSender { + PacketSender::new( + validator_client, + tested_network, + self_address, + test_run_sender, + ) +} + +/// Construct a new gateway client. +pub fn new_gateway_client( + gateway: gateway::Node, + identity_keypair: identity::KeyPair, + ack_sender: AckSender, + mixnet_messages_sender: MixnetSender, +) -> GatewayClient { + let timeout = time::Duration::from_millis(500); + let identity_arc = Arc::new(identity_keypair); + + gateway_client::GatewayClient::new( + gateway.client_listener, + identity_arc, + gateway.identity_key, + None, + mixnet_messages_sender, + ack_sender, + timeout, + ) +} + +fn new_validator_client(validator_rest_uri: &str) -> Arc { + let config = validator_client::Config::new(validator_rest_uri.to_string()); + Arc::new(validator_client::Client::new(config)) +} + +fn new_notifier( + encryption_keypair: encryption::KeyPair, + validator_client: Arc, + mixnet_receiver: MixnetReceiver, + test_run_receiver: TestRunUpdateReceiver, + with_detailed_report: bool, +) -> Notifier { + Notifier::new( + mixnet_receiver, + encryption_keypair, + validator_client, + test_run_receiver, + with_detailed_report, + ) +} + +fn setup_logging() { + let mut log_builder = pretty_env_logger::formatted_timed_builder(); + if let Ok(s) = ::std::env::var("RUST_LOG") { + log_builder.parse_filters(&s); + } else { + // default to 'Info' + log_builder.filter(None, log::LevelFilter::Info); + } + + log_builder + .filter_module("hyper", log::LevelFilter::Warn) + .filter_module("tokio_reactor", log::LevelFilter::Warn) + .filter_module("reqwest", log::LevelFilter::Warn) + .filter_module("mio", log::LevelFilter::Warn) + .filter_module("want", log::LevelFilter::Warn) + .filter_module("sled", log::LevelFilter::Warn) + .filter_module("tungstenite", log::LevelFilter::Warn) + .filter_module("tokio_tungstenite", log::LevelFilter::Warn) + .init(); +} + +fn check_if_up_to_date(v4_topology: &NymTopology, v6_topology: &NymTopology) { + let monitor_version = env!("CARGO_PKG_VERSION"); + for (_, layer_mixes) in v4_topology.mixes().iter() { + for mix in layer_mixes.iter() { + if !version_checker::is_minor_version_compatible(monitor_version, &*mix.version) { + panic!( + "Our good topology is not compatible with monitor! Mix runs {}, we have {}", + mix.version, monitor_version + ) + } + } + } + + for gateway in v4_topology.gateways().iter() { + if !version_checker::is_minor_version_compatible(monitor_version, &*gateway.version) { + panic!( + "Our good topology is not compatible with monitor! Gateway runs {}, we have {}", + gateway.version, monitor_version + ) + } + } + + for (_, layer_mixes) in v6_topology.mixes().iter() { + for mix in layer_mixes.iter() { + if !version_checker::is_minor_version_compatible(monitor_version, &*mix.version) { + panic!( + "Our good topology is not compatible with monitor! Mix runs {}, we have {}", + mix.version, monitor_version + ) + } + } + } + + for gateway in v6_topology.gateways().iter() { + if !version_checker::is_minor_version_compatible(monitor_version, &*gateway.version) { + panic!( + "Our good topology is not compatible with monitor! Gateway runs {}, we have {}", + gateway.version, monitor_version + ) + } + } +} diff --git a/network-monitor/src/monitor.rs b/network-monitor/src/monitor.rs new file mode 100644 index 0000000000..1586e68534 --- /dev/null +++ b/network-monitor/src/monitor.rs @@ -0,0 +1,65 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{notifications::Notifier, packet_sender::PacketSender}; +use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +use log::*; +use tokio::time::{self, Duration}; + +pub(crate) type MixnetReceiver = UnboundedReceiver>>; +pub(crate) type MixnetSender = UnboundedSender>>; +pub(crate) type AckSender = UnboundedSender>>; + +pub(crate) const MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(60); +pub(crate) const NOTIFIER_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20); + +pub struct Monitor; + +impl Monitor { + pub fn new() -> Monitor { + Monitor {} + } + + pub(crate) async fn run(&mut self, mut notifier: Notifier, mut packet_sender: PacketSender) { + println!("Network monitor running - note: 'good' nodes are hardcoded."); + println!("-----------------------------------------------------------"); + tokio::spawn(async move { + notifier.run().await; + }); + + tokio::spawn(async move { + let mut interval = time::interval(MONITOR_RUN_INTERVAL); + loop { + interval.tick().await; + info!(target: "Monitor", "Starting test run"); + + if let Err(err) = packet_sender.run_test().await { + error!("Test run failed! - {:?}", err); + } + } + }); + + self.wait_for_interrupt().await + } + + async fn wait_for_interrupt(&self) { + if let Err(e) = tokio::signal::ctrl_c().await { + error!( + "There was an error while capturing SIGINT - {:?}. We will terminate regardless", + e + ); + } + println!("Received SIGINT - the network monitor will terminate now"); + } +} diff --git a/network-monitor/src/notifications/mod.rs b/network-monitor/src/notifications/mod.rs new file mode 100644 index 0000000000..6539eae5c4 --- /dev/null +++ b/network-monitor/src/notifications/mod.rs @@ -0,0 +1,155 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::monitor::MixnetReceiver; +use crate::monitor::NOTIFIER_DELIVERY_TIMEOUT; +use crate::notifications::test_run::TestRun; +use crate::notifications::test_timeout::TestTimeout; +use crate::run_info::{RunInfo, TestRunUpdate, TestRunUpdateReceiver}; +use crypto::asymmetric::encryption::KeyPair; +use futures::StreamExt; +use log::*; +use nymsphinx::receiver::MessageReceiver; +use std::sync::Arc; +use validator_client::models::mixmining::BatchMixStatus; +use validator_client::ValidatorClientError; + +mod test_run; +mod test_timeout; + +#[derive(Debug)] +enum NotifierError { + ValidatorError(ValidatorClientError), + MalformedPacketReceived, + NonTestPacketReceived, +} + +pub(crate) struct Notifier { + client_encryption_keypair: KeyPair, + message_receiver: MessageReceiver, + mixnet_receiver: MixnetReceiver, + validator_client: Arc, + test_run_receiver: TestRunUpdateReceiver, + test_run_nonce: u64, + current_test_run: TestRun, + test_timeout: TestTimeout, +} + +impl Notifier { + pub(crate) fn new( + mixnet_receiver: MixnetReceiver, + client_encryption_keypair: KeyPair, + validator_client: Arc, + test_run_receiver: TestRunUpdateReceiver, + with_detailed_report: bool, + ) -> Notifier { + let message_receiver = MessageReceiver::new(); + let mut current_test_run = TestRun::new(0).with_report(); + if with_detailed_report { + current_test_run = current_test_run.with_detailed_report(); + } + Notifier { + client_encryption_keypair, + message_receiver, + mixnet_receiver, + validator_client, + test_run_receiver, + test_run_nonce: 0, + current_test_run, + test_timeout: TestTimeout::new(), + } + } + + async fn on_run_start(&mut self, run_info: RunInfo) { + self.test_run_nonce += 1; + + self.current_test_run.refresh(self.test_run_nonce); + self.current_test_run.start_run(run_info); + } + + async fn on_run_end(&mut self) { + let batch_status = self.current_test_run.finish_run(); + if let Err(err) = self.notify_validator(batch_status).await { + warn!("Failed to send batch status to validator - {:?}", err) + } + } + + fn on_sending_over(&mut self, nonce: u64) { + assert_eq!(nonce, self.test_run_nonce); + self.test_timeout.start(NOTIFIER_DELIVERY_TIMEOUT); + } + + async fn on_test_run_update(&mut self, run_update: TestRunUpdate) { + match run_update { + TestRunUpdate::StartSending(run_info) => self.on_run_start(run_info).await, + TestRunUpdate::DoneSending(nonce) => self.on_sending_over(nonce), + } + } + + fn on_mix_messages(&mut self, messages: Vec>) { + for message in messages { + if let Err(err) = self.on_message(message) { + error!(target: "Mix receiver", "failed to process received mix packet - {:?}", err) + } + } + } + + pub(crate) async fn run(&mut self) { + debug!("Started MixnetListener"); + loop { + tokio::select! { + mix_messages = &mut self.mixnet_receiver.next() => { + self.on_mix_messages(mix_messages.expect("mix channel has failed!")); + }, + run_update = &mut self.test_run_receiver.next() => { + self.on_test_run_update(run_update.expect("packet sender has died!")).await; + } + _ = &mut self.test_timeout => { + self.on_run_end().await; + self.test_timeout.clear(); + } + } + } + } + + fn on_message(&mut self, message: Vec) -> Result<(), NotifierError> { + let encrypted_bytes = self + .message_receiver + .recover_plaintext(self.client_encryption_keypair.private_key(), message) + .map_err(|_| NotifierError::MalformedPacketReceived)?; + let fragment = self + .message_receiver + .recover_fragment(&encrypted_bytes) + .map_err(|_| NotifierError::MalformedPacketReceived)?; + let (recovered, _) = self + .message_receiver + .insert_new_fragment(fragment) + .map_err(|_| NotifierError::MalformedPacketReceived)? + .ok_or_else(|| NotifierError::NonTestPacketReceived)?; // if it's a test packet it MUST BE reconstructed with single fragment + + let all_received = self.current_test_run.received_packet(recovered.message); + if all_received { + self.test_timeout.fire(); + } + Ok(()) + } + + async fn notify_validator(&self, status: BatchMixStatus) -> Result<(), NotifierError> { + self.validator_client + .post_batch_mixmining_status(status) + .await + .map_err(NotifierError::ValidatorError)?; + Ok(()) + } +} diff --git a/network-monitor/src/notifications/test_run.rs b/network-monitor/src/notifications/test_run.rs new file mode 100644 index 0000000000..c6dd1c717d --- /dev/null +++ b/network-monitor/src/notifications/test_run.rs @@ -0,0 +1,286 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::run_info::RunInfo; +use crate::test_packet::TestPacket; +use log::*; +use std::collections::{HashMap, HashSet}; +use std::mem; +use validator_client::models::mixmining::{BatchMixStatus, MixStatus}; + +pub(super) struct TestRun { + print_report: bool, + print_detailed_report: bool, + test_report: TestReport, + + run_nonce: u64, + expected_run_packets: HashSet, + received_packets: Vec, +} + +#[derive(Default)] +struct NodeResult { + ip_v4_compatible: bool, + ip_v6_compatible: bool, +} + +#[derive(Default)] +struct TestReport { + total_sent: usize, + total_received: usize, + malformed: Vec, + outdated: Vec<(String, String)>, + + // below are only populated if we're going to be printing the report + only_ipv4_compatible: Vec, // can't speak v6, but can speak v4 + only_ipv6_compatible: Vec, // can't speak v4, but can speak v6 + completely_unroutable: Vec, // can't speak either v4 or v6 + fully_working: Vec, +} + +impl TestReport { + fn print(&self, detailed: bool) { + info!(target: "Test Report", "Sent total of {} packets", self.total_sent); + info!(target: "Test Report", "Received total of {} packets", self.total_received); + info!(target: "Test Report", "{} nodes are malformed", self.malformed.len()); + info!(target: "Test Report", "{} nodes are outdated", self.outdated.len()); + info!(target: "Test Report", "{} nodes speak ONLY IPv4 (NO IPv6 connectivity)", self.only_ipv4_compatible.len()); + info!(target: "Test Report", "{} nodes speak ONLY IPv6 (NO IPv4 connectivity)", self.only_ipv6_compatible.len()); + info!(target: "Test Report", "{} nodes are totally unroutable!", self.completely_unroutable.len()); + info!(target: "Test Report", "{} nodes work fine!", self.fully_working.len()); + + if detailed { + info!(target: "Detailed report", "full summary:"); + for malformed in self.malformed.iter() { + info!(target: "Malformed node", "{}", malformed) + } + for outdated in self.outdated.iter() { + info!(target: "Outdated node", "{} (runs v{})", outdated.0, outdated.1) + } + for v4_node in self.only_ipv4_compatible.iter() { + info!(target: "IPv4-only node", "{}", v4_node) + } + + for v6_node in self.only_ipv6_compatible.iter() { + info!(target: "IPv6-only node", "{}", v6_node) + } + + for unroutable in self.completely_unroutable.iter() { + info!(target: "Unroutable node", "{}", unroutable) + } + + for working in self.fully_working.iter() { + info!(target: "Fully working node", "{}", working) + } + } + } +} + +impl TestRun { + pub(super) fn new(run_nonce: u64) -> Self { + TestRun { + print_report: false, + print_detailed_report: false, + test_report: Default::default(), + run_nonce, + expected_run_packets: Default::default(), + received_packets: vec![], + } + } + + pub(super) fn with_report(mut self) -> Self { + self.print_report = true; + self + } + + pub(super) fn with_detailed_report(mut self) -> Self { + self.print_report = true; + self.print_detailed_report = true; + self + } + + pub(super) fn refresh(&mut self, new_nonce: u64) { + self.test_report = Default::default(); + self.run_nonce = new_nonce; + self.expected_run_packets = Default::default(); + self.received_packets = Default::default(); + } + + fn down_status(&self, pub_key: String) -> Vec { + let v4_status = MixStatus { + pub_key: pub_key.clone(), + ip_version: "4".to_string(), + up: false, + }; + + let v6_status = MixStatus { + pub_key, + ip_version: "6".to_string(), + up: false, + }; + + let mut vec = Vec::with_capacity(2); + vec.push(v4_status); + vec.push(v6_status); + vec + } + + /// Update state of self based on the received `RunInfo` + pub(super) fn start_run(&mut self, run_info: RunInfo) { + if run_info.nonce != self.run_nonce { + error!( + "Received unexpected test run info! Got {}, expected: {}", + self.run_nonce, run_info.nonce + ); + return; + } + + // notify about malformed nodes: + for malformed_mix in run_info.malformed_mixes { + debug!( + target: "test-run", + "{} is malformed", malformed_mix.clone() + ); + self.test_report.malformed.push(malformed_mix); + } + + for old_mix in run_info.incompatible_mixes { + debug!( + target: "test-run", + "{} is outdated! It's on {} version", + old_mix.0.clone(), + old_mix.1 + ); + self.test_report.outdated.push(old_mix); + } + + self.test_report.total_sent = run_info.test_packets.len(); + + // store information about packets that are currently being sent + self.expected_run_packets + .reserve(run_info.test_packets.len()); + for test_packet in run_info.test_packets { + self.expected_run_packets.insert(test_packet); + } + } + + pub(super) fn received_packet(&mut self, message: Vec) -> bool { + let test_packet = match TestPacket::try_from_bytes(&message) { + Ok(packet) => packet, + Err(err) => { + warn!("Invalid test packet received - {:?}", err); + return false; + } + }; + + if test_packet.nonce() == self.run_nonce { + self.received_packets.push(test_packet); + } else { + warn!( + "Received test packet for different test run! (Got {}, expected {})", + test_packet.nonce(), + self.run_nonce + ); + } + + self.received_packets.len() == self.expected_run_packets.len() + } + + fn produce_summary(&self) -> HashMap { + // contains map of all (seemingly valid) nodes and whether they speak ipv4/ipv6 + let mut summary: HashMap = HashMap::new(); + + // update based on data we actually get + for received_status in self.received_packets.iter() { + let entry = summary.entry(received_status.pub_key_string()).or_default(); + if received_status.ip_version().is_v4() { + entry.ip_v4_compatible = true + } else { + entry.ip_v6_compatible = true + } + } + + // and then insert entries we didn't get but should have + for expected in self.expected_run_packets.iter() { + summary.entry(expected.pub_key_string()).or_default(); + } + + summary + } + + fn finalize_report(&mut self) { + let mut fully_working = Vec::new(); + let mut only_v4_compatible = Vec::new(); + let mut only_v6_compatible = Vec::new(); + let mut unroutable_nodes = Vec::new(); + + let summary = self.produce_summary(); + for (node, result) in summary.into_iter() { + if result.ip_v4_compatible && result.ip_v6_compatible { + fully_working.push(node) + } else if result.ip_v4_compatible { + only_v4_compatible.push(node) + } else if result.ip_v6_compatible { + only_v6_compatible.push(node) + } else { + unroutable_nodes.push(node) + } + } + + self.test_report.fully_working = fully_working; + self.test_report.only_ipv4_compatible = only_v4_compatible; + self.test_report.only_ipv6_compatible = only_v6_compatible; + self.test_report.completely_unroutable = unroutable_nodes; + } + + pub(super) fn finish_run(&mut self) -> BatchMixStatus { + self.test_report.total_received = self.received_packets.len(); + + if self.print_report { + self.finalize_report(); + self.test_report.print(self.print_detailed_report); + } + + let mut mix_status = Vec::with_capacity( + 2 * (self.test_report.malformed.len() + self.test_report.outdated.len()) + + self.expected_run_packets.len(), + ); + + // firstly we know all malformed and outdated nodes are definitely down - we haven't sent + // any test packets for those + for malformed in self.test_report.malformed.iter() { + let mut down_status = self.down_status(malformed.clone()); + mix_status.append(&mut down_status); + } + for outdated in self.test_report.outdated.iter() { + let mut down_status = self.down_status(outdated.0.clone()); + mix_status.append(&mut down_status); + } + + let mut undelivered = mem::replace(&mut self.expected_run_packets, HashSet::new()); + + // then create status for packets we actually received + for received in mem::replace(&mut self.received_packets, Vec::new()) { + undelivered.remove(&received); + mix_status.push(received.into_up_mixstatus()) + } + + // and finally create status for packets we sent but never received + for undelivered_packet in undelivered.into_iter() { + mix_status.push(undelivered_packet.into_down_mixstatus()) + } + + BatchMixStatus { status: mix_status } + } +} diff --git a/network-monitor/src/notifications/test_timeout.rs b/network-monitor/src/notifications/test_timeout.rs new file mode 100644 index 0000000000..655d2accf4 --- /dev/null +++ b/network-monitor/src/notifications/test_timeout.rs @@ -0,0 +1,57 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::*; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::time::{delay_for, Delay, Duration, Instant}; + +pub(super) struct TestTimeout { + delay: Option, +} + +impl TestTimeout { + pub(super) fn new() -> Self { + TestTimeout { delay: None } + } + + pub(super) fn start(&mut self, duration: Duration) { + self.delay = Some(delay_for(duration)) + } + + pub(super) fn clear(&mut self) { + self.delay = None + } + + /// Forces self to fire regardless of internal Delay state + pub(super) fn fire(&mut self) { + match self.delay.as_mut() { + None => error!("Tried to fire non-existent delay!"), + // just set the next delay to 0 so it will be polled immediately and be already elapsed + Some(delay) => delay.reset(Instant::now()), + } + } +} + +impl Future for TestTimeout { + type Output = ::Output; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.delay.as_mut() { + None => Poll::Pending, + Some(delay) => Pin::new(delay).poll(cx), + } + } +} diff --git a/network-monitor/src/packet_sender.rs b/network-monitor/src/packet_sender.rs new file mode 100644 index 0000000000..3a24f48e7b --- /dev/null +++ b/network-monitor/src/packet_sender.rs @@ -0,0 +1,199 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::chunker::Chunker; +use crate::run_info::{RunInfo, TestRunUpdate, TestRunUpdateSender}; +use crate::test_packet::{IpVersion, TestPacket}; +use crate::tested_network::{TestMix, TestedNetwork}; +use gateway_client::error::GatewayClientError; +use log::*; +use nymsphinx::addressing::clients::Recipient; +use nymsphinx::forwarding::packet::MixPacket; +use std::convert::TryInto; +use std::sync::Arc; +use topology::mix; +use validator_client::models::mixnode::RegisteredMix; +use validator_client::ValidatorClientError; + +#[derive(Debug)] +pub(crate) enum PacketSenderError { + ValidatorError(ValidatorClientError), + GatewayError(GatewayClientError), +} + +impl From for PacketSenderError { + fn from(err: GatewayClientError) -> Self { + PacketSenderError::GatewayError(err) + } +} + +pub struct PacketSender { + chunker: Chunker, + validator_client: Arc, + tested_network: TestedNetwork, + test_run_sender: TestRunUpdateSender, + nonce: u64, +} + +impl PacketSender { + pub(crate) fn new( + validator_client: Arc, + tested_network: TestedNetwork, + self_address: Recipient, + test_run_sender: TestRunUpdateSender, + ) -> Self { + PacketSender { + chunker: Chunker::new(self_address), + validator_client, + tested_network, + test_run_sender, + nonce: 0, + } + } + + fn make_test_mix(&self, mix: RegisteredMix) -> TestMix { + // the reason for that conversion is that I want to operate on concrete types + // rather than on "String" everywhere and also this way we remove obviously wrong + // mixnodes where somebody is sending bullshit presence data. + let mix_id = mix.identity(); + let mix: Result = mix.try_into(); + match mix { + Err(err) => { + error!("mix {} is malformed - {:?}", mix_id, err); + TestMix::MalformedMix(mix_id) + } + Ok(mix) => { + if version_checker::is_minor_version_compatible( + &mix.version, + self.tested_network.system_version(), + ) { + let v4_test_packet = + TestPacket::new(mix.identity_key, IpVersion::V4, self.nonce); + let v6_test_packet = + TestPacket::new(mix.identity_key, IpVersion::V6, self.nonce); + + TestMix::ValidMix(mix, [v4_test_packet, v6_test_packet]) + } else { + TestMix::IncompatibleMix(mix) + } + } + } + } + + async fn get_test_mixes(&self) -> Result, PacketSenderError> { + Ok(self + .validator_client + .get_topology() + .await + .map_err(PacketSenderError::ValidatorError)? + .mix_nodes + .into_iter() + .map(|mix| self.make_test_mix(mix)) + .collect()) + } + + fn prepare_run_info(&self, test_mixes: &[TestMix]) -> RunInfo { + let num_valid = test_mixes.iter().filter(|mix| mix.is_valid()).count(); + let mut test_packets = Vec::with_capacity(num_valid * 2); + let mut malformed_mixes = Vec::new(); + let mut incompatible_mixes = Vec::new(); + + for test_mix in test_mixes { + match test_mix { + TestMix::ValidMix(.., mix_test_packets) => { + test_packets.push(mix_test_packets[0]); + test_packets.push(mix_test_packets[1]); + } + TestMix::MalformedMix(pub_key) => malformed_mixes.push(pub_key.clone()), + TestMix::IncompatibleMix(mix) => incompatible_mixes + .push((mix.identity_key.to_base58_string(), mix.version.clone())), + } + } + RunInfo { + nonce: self.nonce, + test_packets, + malformed_mixes, + incompatible_mixes, + } + } + + async fn prepare_node_mix_packets( + &mut self, + mixnode: mix::Node, + test_packets: [TestPacket; 2], + ) -> Vec { + let mut packets = Vec::with_capacity(2); + for test_packet in test_packets.iter() { + let topology_to_test = self + .tested_network + .substitute_node(mixnode.clone(), test_packet.ip_version()); + let mix_message = test_packet.to_bytes(); + let mut mix_packet = self + .chunker + .prepare_messages(mix_message, &topology_to_test) + .await; + debug_assert_eq!(mix_packet.len(), 1); + packets.push(mix_packet.pop().unwrap()); + } + packets + } + + async fn prepare_mix_packets(&mut self, test_mixes: Vec) -> Vec { + let num_valid = test_mixes.iter().filter(|mix| mix.is_valid()).count(); + let mut mix_packets = Vec::with_capacity(num_valid); + + for test_mix in test_mixes { + match test_mix { + TestMix::ValidMix(mixnode, test_packets) => { + let mut node_mix_packets = + self.prepare_node_mix_packets(mixnode, test_packets).await; + mix_packets.append(&mut node_mix_packets); + } + _ => continue, + } + } + mix_packets + } + + async fn send_messages( + &mut self, + mix_packets: Vec, + ) -> Result<(), PacketSenderError> { + self.tested_network.send_messages(mix_packets).await?; + Ok(()) + } + + pub(crate) async fn run_test(&mut self) -> Result<(), PacketSenderError> { + self.nonce += 1; + + let test_mixes = self.get_test_mixes().await?; + info!(target: "Monitor", "Going to test {} mixes", test_mixes.len()); + let run_info = self.prepare_run_info(&test_mixes); + let mix_packets = self.prepare_mix_packets(test_mixes).await; + + // inform notifier that we're about to start the test + self.test_run_sender + .unbounded_send(TestRunUpdate::StartSending(run_info)) + .expect("notifier has crashed!"); + + self.send_messages(mix_packets).await?; + + // inform the notifier we're done sending (so that it should start its timeout) + self.test_run_sender + .unbounded_send(TestRunUpdate::DoneSending(self.nonce)) + .expect("notifier has crashed!"); + + Ok(()) + } +} diff --git a/common/topology/src/coco.rs b/network-monitor/src/run_info.rs similarity index 54% rename from common/topology/src/coco.rs rename to network-monitor/src/run_info.rs index ebf214d30e..4fbe463006 100644 --- a/common/topology/src/coco.rs +++ b/network-monitor/src/run_info.rs @@ -12,20 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::filter; -use crypto::asymmetric::identity; +use crate::test_packet::TestPacket; +use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; -#[derive(Debug, Clone)] -pub struct Node { - pub location: String, - pub host: String, - pub pub_key: identity::PublicKey, - pub last_seen: u64, - pub version: String, +pub(crate) type TestRunUpdateSender = UnboundedSender; +pub(crate) type TestRunUpdateReceiver = UnboundedReceiver; + +pub(crate) struct RunInfo { + pub nonce: u64, + pub test_packets: Vec, + pub malformed_mixes: Vec, + pub incompatible_mixes: Vec<(String, String)>, } -impl filter::Versioned for Node { - fn version(&self) -> String { - self.version.clone() - } +pub(crate) enum TestRunUpdate { + StartSending(RunInfo), + DoneSending(u64), } diff --git a/network-monitor/src/test_packet.rs b/network-monitor/src/test_packet.rs new file mode 100644 index 0000000000..977330dd78 --- /dev/null +++ b/network-monitor/src/test_packet.rs @@ -0,0 +1,173 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crypto::asymmetric::{encryption, identity}; +use std::convert::{TryFrom, TryInto}; +use std::fmt::{self, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::mem; +use validator_client::models::mixmining::MixStatus; + +#[derive(Debug)] +pub(crate) enum TestPacketError { + IncompletePacket, + InvalidIpVersion, + InvalidNodeKey, +} + +impl From for TestPacketError { + fn from(_: identity::KeyRecoveryError) -> Self { + TestPacketError::InvalidNodeKey + } +} + +#[repr(u8)] +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub(crate) enum IpVersion { + V4 = 4, + V6 = 6, +} + +impl TryFrom for IpVersion { + type Error = TestPacketError; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (Self::V4 as u8) => Ok(Self::V4), + _ if value == (Self::V6 as u8) => Ok(Self::V6), + _ => Err(TestPacketError::InvalidIpVersion), + } + } +} + +impl IpVersion { + pub(crate) fn is_v4(&self) -> bool { + *self == IpVersion::V4 + } +} + +impl Into for IpVersion { + fn into(self) -> String { + format!("{}", self) + } +} + +impl Display for IpVersion { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", *self as u8) + } +} + +#[derive(Eq, Copy, Clone, Debug)] +pub(crate) struct TestPacket { + ip_version: IpVersion, + nonce: u64, + pub_key: identity::PublicKey, +} + +impl Display for TestPacket { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "TestPacket {{ ip: {}, pub_key: {}, nonce: {} }}", + self.ip_version, + self.pub_key.to_base58_string(), + self.nonce + ) + } +} + +impl Hash for TestPacket { + fn hash(&self, state: &mut H) { + self.ip_version.hash(state); + self.nonce.hash(state); + self.pub_key.to_bytes().hash(state); + } +} + +impl PartialEq for TestPacket { + fn eq(&self, other: &Self) -> bool { + self.ip_version == other.ip_version + && self.nonce == other.nonce + && self.pub_key.to_bytes() == other.pub_key.to_bytes() + } +} + +impl TestPacket { + pub(crate) fn new(pub_key: identity::PublicKey, ip_version: IpVersion, nonce: u64) -> Self { + TestPacket { + pub_key, + ip_version, + nonce, + } + } + + pub(crate) fn nonce(&self) -> u64 { + self.nonce + } + + pub(crate) fn ip_version(&self) -> IpVersion { + self.ip_version + } + + pub(crate) fn pub_key_string(&self) -> String { + self.pub_key.to_base58_string() + } + + pub(crate) fn to_bytes(&self) -> Vec { + self.nonce + .to_be_bytes() + .iter() + .cloned() + .chain(std::iter::once(self.ip_version as u8)) + .chain(self.pub_key.to_bytes().iter().cloned()) + .collect() + } + + pub(crate) fn try_from_bytes(b: &[u8]) -> Result { + // nonce size + let n = mem::size_of::(); + + if b.len() != n + 1 + encryption::PUBLIC_KEY_SIZE { + return Err(TestPacketError::IncompletePacket); + } + + // this unwrap can't fail as we've already checked for the size + let nonce = u64::from_be_bytes(b[0..n].try_into().unwrap()); + let ip_version = IpVersion::try_from(b[n])?; + let pub_key = identity::PublicKey::from_bytes(&b[n + 1..])?; + + Ok(TestPacket { + ip_version, + nonce, + pub_key, + }) + } + + pub(crate) fn into_up_mixstatus(self) -> MixStatus { + MixStatus { + pub_key: self.pub_key.to_base58_string(), + ip_version: self.ip_version.into(), + up: true, + } + } + + pub(crate) fn into_down_mixstatus(self) -> MixStatus { + MixStatus { + pub_key: self.pub_key.to_base58_string(), + ip_version: self.ip_version.into(), + up: false, + } + } +} diff --git a/network-monitor/src/tested_network/good_topology.rs b/network-monitor/src/tested_network/good_topology.rs new file mode 100644 index 0000000000..bcbcb44f82 --- /dev/null +++ b/network-monitor/src/tested_network/good_topology.rs @@ -0,0 +1,33 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fs; +use topology::NymTopology; +use validator_client::models::topology::Topology; + +pub(crate) fn parse_topology_file(file_path: &str) -> NymTopology { + let file_content = + fs::read_to_string(file_path).expect("specified topology file does not exist"); + let validator_topology = serde_json::from_str::(&file_content) + .expect("topology in specified file is malformed"); + let nym_topology: NymTopology = validator_topology.into(); + if nym_topology.mixes().len() != 3 { + panic!("topology has different than 3 number of layers") + } + if nym_topology.gateways().is_empty() { + panic!("topology does not include a gateway") + } + + nym_topology +} diff --git a/network-monitor/src/tested_network/mod.rs b/network-monitor/src/tested_network/mod.rs new file mode 100644 index 0000000000..5f2722a34d --- /dev/null +++ b/network-monitor/src/tested_network/mod.rs @@ -0,0 +1,86 @@ +// Copyright 2020 Nym Technologies SA +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::test_packet::{IpVersion, TestPacket}; +use gateway_client::error::GatewayClientError; +use gateway_client::GatewayClient; +use nymsphinx::forwarding::packet::MixPacket; +use topology::{mix, NymTopology}; + +pub(crate) mod good_topology; + +pub(crate) enum TestMix { + ValidMix(mix::Node, [TestPacket; 2]), + IncompatibleMix(mix::Node), + MalformedMix(String), +} + +impl TestMix { + pub(crate) fn is_valid(&self) -> bool { + matches!(self, TestMix::ValidMix(..)) + } +} + +pub(crate) struct TestedNetwork { + system_version: String, + gateway_client: GatewayClient, + good_v4_topology: NymTopology, + good_v6_topology: NymTopology, +} + +impl TestedNetwork { + pub(crate) fn new_good( + gateway_client: GatewayClient, + good_v4_topology: NymTopology, + good_v6_topology: NymTopology, + ) -> Self { + TestedNetwork { + system_version: good_v4_topology.mixes()[&1][0].version.clone(), + gateway_client, + good_v4_topology, + good_v6_topology, + } + } + + pub(crate) fn system_version(&self) -> &str { + &self.system_version + } + + pub(crate) async fn start_gateway_client(&mut self) { + self.gateway_client + .authenticate_and_start() + .await + .expect("Couldn't authenticate with gateway node."); + } + + pub(crate) async fn send_messages( + &mut self, + mix_packets: Vec, + ) -> Result<(), GatewayClientError> { + self.gateway_client + .batch_send_mix_packets(mix_packets) + .await?; + Ok(()) + } + + pub(crate) fn substitute_node(&self, node: mix::Node, ip_version: IpVersion) -> NymTopology { + let mut good_topology = match ip_version { + IpVersion::V4 => self.good_v4_topology.clone(), + IpVersion::V6 => self.good_v6_topology.clone(), + }; + + good_topology.set_mixes_in_layer(node.layer as u8, vec![node]); + good_topology + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..48e341a095 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/scripts/generate_changelog.sh b/scripts/generate_changelog.sh index 18e3806df4..f2efef64be 100755 --- a/scripts/generate_changelog.sh +++ b/scripts/generate_changelog.sh @@ -1,3 +1,4 @@ +#!/bin/bash # Copyright 2020 Nym # @@ -13,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/bin/bash # set CHANGELOG_GITHUB_TOKEN in your .bashrc file # For each version, you can add a release summary with text, images, gif animations, etc, and show new features and notes clearly to the user. This is done using GitHub metadata. diff --git a/scripts/systemd/nym-mixnode.service b/scripts/systemd/nym-mixnode.service index 2d76cb9e8e..aa0a883d1f 100644 --- a/scripts/systemd/nym-mixnode.service +++ b/scripts/systemd/nym-mixnode.service @@ -43,6 +43,7 @@ User=nym Group=nym ExecStart=/home/nym/nym-mixnode_linux_x86_64 run --id iamboss Restart=on-abort +KillSignal=SIGINT [Install] WantedBy=multi-user.target diff --git a/service-providers/sphinx-socks/Cargo.toml b/service-providers/network-requester/Cargo.toml similarity index 94% rename from service-providers/sphinx-socks/Cargo.toml rename to service-providers/network-requester/Cargo.toml index 2a03293dfb..cbb40db543 100644 --- a/service-providers/sphinx-socks/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "sphinx-socks" -version = "0.8.1" +name = "nym-network-requester" +version = "0.9.0" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] edition = "2018" diff --git a/service-providers/sphinx-socks/allowed.list.sample b/service-providers/network-requester/allowed.list.sample similarity index 80% rename from service-providers/sphinx-socks/allowed.list.sample rename to service-providers/network-requester/allowed.list.sample index bbfd80bcc1..2c2197899c 100644 --- a/service-providers/sphinx-socks/allowed.list.sample +++ b/service-providers/network-requester/allowed.list.sample @@ -1,9 +1,11 @@ blockstream.info +greenaddress.it +electrum.org qtornado.com aranguren.org hsmiths.com not.fyi cluelessperson.com bauerj.eu -electrum.org -greenaddress.it +keybaseapi.com +amazonaws.com diff --git a/service-providers/sphinx-socks/src/allowed_hosts.rs b/service-providers/network-requester/src/allowed_hosts.rs similarity index 89% rename from service-providers/sphinx-socks/src/allowed_hosts.rs rename to service-providers/network-requester/src/allowed_hosts.rs index 51e7908358..076074fb14 100644 --- a/service-providers/sphinx-socks/src/allowed_hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts.rs @@ -54,7 +54,7 @@ impl OutboundRequestFilter { match self.get_domain_root(&trimmed) { Some(domain_root) => { if self.allowed_hosts.contains(&domain_root) { - return true; + true } else { // not in allowed list but it's a domain log::warn!( @@ -62,21 +62,20 @@ impl OutboundRequestFilter { &domain_root ); self.unknown_hosts.maybe_add(&domain_root); - return false; // domain is unknown + false // domain is unknown } } None => { - return false; // the host was either an IP or nonsense. For this release, we'll ignore it. + false // the host was either an IP or nonsense. For this release, we'll ignore it. } - }; + } } fn trim_port(host: &str) -> String { - let mut tmp: Vec<&str> = host.split(":").collect(); + let mut tmp: Vec<&str> = host.split(':').collect(); if tmp.len() > 1 { tmp.pop(); // get rid of last element (port) - let out = tmp.join(":"); //rejoin - out + tmp.join(":") //rejoin } else { host.to_string() } @@ -86,12 +85,12 @@ impl OutboundRequestFilter { fn get_domain_root(&self, host: &str) -> Option { match self.domain_list.parse_domain(host) { Ok(d) => match d.root() { - Some(root) => return Some(root.to_string()), - None => return None, // no domain root matches + Some(root) => Some(root.to_string()), + None => None, // no domain root matches }, Err(_) => { log::warn!("Error parsing domain: {:?}", host); - return None; // domain couldn't be parsed + None // domain couldn't be parsed } } } @@ -108,10 +107,8 @@ impl HostsStore { /// Constructs a new HostsStore pub(crate) fn new(base_dir: PathBuf, filename: PathBuf) -> HostsStore { let storefile = HostsStore::setup_storefile(base_dir, filename); - let hosts = HostsStore::load_from_storefile(&storefile).expect(&format!( - "Could not load hosts from storefile at {:?}", - storefile - )); + let hosts = HostsStore::load_from_storefile(&storefile) + .unwrap_or_else(|_| panic!("Could not load hosts from storefile at {:?}", storefile)); HostsStore { storefile, hosts } } @@ -153,11 +150,9 @@ impl HostsStore { } fn setup_storefile(base_dir: PathBuf, filename: PathBuf) -> PathBuf { - let dirpath = base_dir.join("service-providers").join("sphinx-socks"); - fs::create_dir_all(&dirpath).expect(&format!( - "could not create storage directory at {:?}", - dirpath - )); + let dirpath = base_dir.join("service-providers").join("network-requester"); + fs::create_dir_all(&dirpath) + .unwrap_or_else(|_| panic!("could not create storage directory at {:?}", dirpath)); let storefile = dirpath.join(filename); let exists = std::path::Path::new(&storefile).exists(); if !exists { @@ -208,7 +203,7 @@ mod tests { let allowed_filename = PathBuf::from(format!("allowed-{}.list", random_string())); let unknown_filename = PathBuf::from(&format!("unknown-{}.list", random_string())); let allowed = HostsStore::new(base_dir.clone(), allowed_filename); - let unknown = HostsStore::new(base_dir.clone(), unknown_filename); + let unknown = HostsStore::new(base_dir, unknown_filename); OutboundRequestFilter::new(allowed, unknown) } @@ -270,7 +265,7 @@ mod tests { let allowed_filename = PathBuf::from(format!("allowed-{}.list", random_string())); let unknown_filename = PathBuf::from(&format!("unknown-{}.list", random_string())); let allowed = HostsStore::new(base_dir.clone(), allowed_filename); - let unknown = HostsStore::new(base_dir.clone(), unknown_filename); + let unknown = HostsStore::new(base_dir, unknown_filename); OutboundRequestFilter::new(allowed, unknown) } @@ -301,8 +296,8 @@ mod tests { let (_, base_dir2, unknown_filename) = create_test_storefile(); HostsStore::append(&allowed_storefile, "nymtech.net"); - let allowed = HostsStore::new(base_dir1, allowed_filename.to_path_buf()); - let unknown = HostsStore::new(base_dir2, unknown_filename.to_path_buf()); + let allowed = HostsStore::new(base_dir1, allowed_filename); + let unknown = HostsStore::new(base_dir2, unknown_filename); OutboundRequestFilter::new(allowed, unknown) } #[test] @@ -348,11 +343,9 @@ mod tests { fn create_test_storefile() -> (PathBuf, PathBuf, PathBuf) { let base_dir = test_base_dir(); let filename = PathBuf::from(format!("hosts-store-{}.list", random_string())); - let dirpath = base_dir.join("service-providers").join("sphinx-socks"); - fs::create_dir_all(&dirpath).expect(&format!( - "could not create storage directory at {:?}", - dirpath - )); + let dirpath = base_dir.join("service-providers").join("network-requester"); + fs::create_dir_all(&dirpath) + .unwrap_or_else(|_| panic!("could not create storage directory at {:?}", dirpath)); let storefile = dirpath.join(&filename); File::create(&storefile).unwrap(); (storefile, base_dir, filename) diff --git a/service-providers/sphinx-socks/src/connection.rs b/service-providers/network-requester/src/connection.rs similarity index 77% rename from service-providers/sphinx-socks/src/connection.rs rename to service-providers/network-requester/src/connection.rs index 36b7ba40e9..ef442f4157 100644 --- a/service-providers/sphinx-socks/src/connection.rs +++ b/service-providers/network-requester/src/connection.rs @@ -1,7 +1,5 @@ use futures::channel::mpsc; -use log::*; use nymsphinx::addressing::clients::Recipient; -use ordered_buffer::OrderedMessageSender; use proxy_helpers::connection_controller::ConnectionReceiver; use proxy_helpers::proxy_runner::ProxyRunner; use socks5_requests::{ConnectionId, RemoteAddress, Response}; @@ -23,18 +21,9 @@ impl Connection { pub(crate) async fn new( id: ConnectionId, address: RemoteAddress, - initial_data: &[u8], return_address: Recipient, ) -> io::Result { - let mut conn = TcpStream::connect(&address).await?; - - // write the initial data to the connection before continuing - info!( - "Sending initial {} bytes to {}", - initial_data.len(), - address - ); - conn.write_all(initial_data).await?; + let conn = TcpStream::connect(&address).await?; Ok(Connection { id, @@ -50,15 +39,16 @@ impl Connection { mix_sender: mpsc::UnboundedSender<(Response, Recipient)>, ) { let stream = self.conn.take().unwrap(); - let message_sender = OrderedMessageSender::new(); + let remote_source_address = "???".to_string(); // we don't know ip address of requester let connection_id = self.id; let recipient = self.return_address; let (stream, _) = ProxyRunner::new( stream, + self.address.clone(), + remote_source_address, mix_receiver, mix_sender, connection_id, - message_sender, ) .run(move |conn_id, read_data, socket_closed| { (Response::new(conn_id, read_data, socket_closed), recipient) diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs new file mode 100644 index 0000000000..18de587544 --- /dev/null +++ b/service-providers/network-requester/src/core.rs @@ -0,0 +1,276 @@ +use crate::allowed_hosts::{HostsStore, OutboundRequestFilter}; +use crate::connection::Connection; +use crate::websocket; +use futures::channel::mpsc; +use futures::stream::{SplitSink, SplitStream}; +use futures::{SinkExt, StreamExt}; +use log::*; +use nymsphinx::addressing::clients::Recipient; +use nymsphinx::receiver::ReconstructedMessage; +use proxy_helpers::connection_controller::{Controller, ControllerCommand, ControllerSender}; +use socks5_requests::{ConnectionId, Request, Response}; +use std::path::PathBuf; +use std::sync::atomic::{AtomicUsize, Ordering}; +use tokio::net::TcpStream; +use tokio_tungstenite::tungstenite::protocol::Message; +use tokio_tungstenite::WebSocketStream; +use websocket::WebsocketConnectionError; +use websocket_requests::{requests::ClientRequest, responses::ServerResponse}; + +// Since it's an atomic, it's safe to be kept static and shared across threads +static ACTIVE_PROXIES: AtomicUsize = AtomicUsize::new(0); + +pub struct ServiceProvider { + listening_address: String, + outbound_request_filter: OutboundRequestFilter, + open_proxy: bool, +} + +impl ServiceProvider { + pub fn new(listening_address: String, open_proxy: bool) -> ServiceProvider { + let allowed_hosts = HostsStore::new( + HostsStore::default_base_dir(), + PathBuf::from("allowed.list"), + ); + + let unknown_hosts = HostsStore::new( + HostsStore::default_base_dir(), + PathBuf::from("unknown.list"), + ); + let outbound_request_filter = OutboundRequestFilter::new(allowed_hosts, unknown_hosts); + ServiceProvider { + listening_address, + outbound_request_filter, + open_proxy, + } + } + + /// Listens for any messages from `mix_reader` that should be written back to the mix network + /// via the `websocket_writer`. + async fn mixnet_response_listener( + mut websocket_writer: SplitSink, Message>, + mut mix_reader: mpsc::UnboundedReceiver<(Response, Recipient)>, + ) { + // TODO: wire SURBs in here once they're available + while let Some((response, return_address)) = mix_reader.next().await { + // make 'request' to native-websocket client + let response_message = ClientRequest::Send { + recipient: return_address, + message: response.into_bytes(), + with_reply_surb: false, + }; + + let message = Message::Binary(response_message.serialize()); + websocket_writer.send(message).await.unwrap(); + } + } + + async fn read_websocket_message( + websocket_reader: &mut SplitStream>, + ) -> Option { + while let Some(msg) = websocket_reader.next().await { + let data = msg + .expect("we failed to read from the websocket!") + .into_data(); + + // try to recover the actual message from the mix network... + let deserialized_message = match ServerResponse::deserialize(&data) { + Ok(deserialized) => deserialized, + Err(err) => { + error!( + "Failed to deserialize received websocket message! - {}", + err + ); + continue; + } + }; + + let received = match deserialized_message { + ServerResponse::Received(received) => received, + ServerResponse::Error(err) => { + panic!("received error from native client! - {}", err) + } + _ => unimplemented!("probably should never be reached?"), + }; + return Some(received); + } + None + } + + async fn start_proxy( + conn_id: ConnectionId, + remote_addr: String, + return_address: Recipient, + controller_sender: ControllerSender, + mix_input_sender: mpsc::UnboundedSender<(Response, Recipient)>, + ) { + let mut conn = match Connection::new(conn_id, remote_addr.clone(), return_address).await { + Ok(conn) => conn, + Err(err) => { + error!( + "error while connecting to {:?} ! - {:?}", + remote_addr.clone(), + err + ); + return; + } + }; + + // Connect implies it's a fresh connection - register it with our controller + let (mix_sender, mix_receiver) = mpsc::unbounded(); + controller_sender + .unbounded_send(ControllerCommand::Insert(conn_id, mix_sender)) + .unwrap(); + + let old_count = ACTIVE_PROXIES.fetch_add(1, Ordering::SeqCst); + info!( + "Starting proxy for {} (currently there are {} proxies being handled)", + remote_addr, + old_count + 1 + ); + + // run the proxy on the connection + conn.run_proxy(mix_receiver, mix_input_sender).await; + + // proxy is done - remove the access channel from the controller + controller_sender + .unbounded_send(ControllerCommand::Remove(conn_id)) + .unwrap(); + + let old_count = ACTIVE_PROXIES.fetch_sub(1, Ordering::SeqCst); + info!( + "Proxy for {} is finished (currently there are {} proxies being handled)", + remote_addr, + old_count - 1 + ); + } + + fn handle_proxy_connect( + &mut self, + controller_sender: &mut ControllerSender, + mix_input_sender: &mpsc::UnboundedSender<(Response, Recipient)>, + conn_id: ConnectionId, + remote_addr: String, + return_address: Recipient, + ) { + if !self.open_proxy && !self.outbound_request_filter.check(&remote_addr) { + log::info!("Domain {:?} failed filter check", remote_addr); + return; + } + + let controller_sender_clone = controller_sender.clone(); + let mix_input_sender_clone = mix_input_sender.clone(); + + // and start the proxy for this connection + tokio::spawn(async move { + Self::start_proxy( + conn_id, + remote_addr, + return_address, + controller_sender_clone, + mix_input_sender_clone, + ) + .await + }); + } + + fn handle_proxy_send( + &self, + controller_sender: &mut ControllerSender, + conn_id: ConnectionId, + data: Vec, + closed: bool, + ) { + controller_sender + .unbounded_send(ControllerCommand::Send(conn_id, data, closed)) + .unwrap() + } + + fn handle_proxy_request( + &mut self, + raw_request: &[u8], + controller_sender: &mut ControllerSender, + mix_input_sender: &mpsc::UnboundedSender<(Response, Recipient)>, + ) { + // try to treat each received mix message as a service provider request + let deserialized_request = match Request::try_from_bytes(&raw_request) { + Ok(request) => request, + Err(err) => { + error!("Failed to deserialized received request! - {}", err); + return; + } + }; + + match deserialized_request { + Request::Connect { + conn_id, + remote_addr, + return_address, + } => self.handle_proxy_connect( + controller_sender, + mix_input_sender, + conn_id, + remote_addr, + return_address, + ), + Request::Send(conn_id, data, closed) => { + self.handle_proxy_send(controller_sender, conn_id, data, closed) + } + } + } + + /// Start all subsystems + pub async fn run(&mut self) { + let websocket_stream = self.connect_websocket(&self.listening_address).await; + + // split the websocket so that we could read and write from separate threads + let (websocket_writer, mut websocket_reader) = websocket_stream.split(); + + // channels responsible for managing messages that are to be sent to the mix network. The receiver is + // going to be used by `mixnet_response_listener` + let (mix_input_sender, mix_input_receiver) = mpsc::unbounded::<(Response, Recipient)>(); + + // controller for managing all active connections + let (mut active_connections_controller, mut controller_sender) = Controller::new(); + tokio::spawn(async move { + active_connections_controller.run().await; + }); + + // start the listener for mix messages + tokio::spawn(async move { + Self::mixnet_response_listener(websocket_writer, mix_input_receiver).await; + }); + + println!("\nAll systems go. Press CTRL-C to stop the server."); + + // for each incoming message from the websocket... (which in 99.99% cases is going to be a mix message) + loop { + let received = match Self::read_websocket_message(&mut websocket_reader).await { + Some(msg) => msg, + None => { + error!("The websocket stream has finished!"); + return; + } + }; + + let raw_message = received.message; + // TODO: here be potential SURB (i.e. received.reply_SURB) + + self.handle_proxy_request(&raw_message, &mut controller_sender, &mix_input_sender) + } + } + + // Make the websocket connection so we can receive incoming Mixnet messages. + async fn connect_websocket(&self, uri: &str) -> WebSocketStream { + let ws_stream = match websocket::Connection::new(uri).connect().await { + Ok(ws_stream) => { + info!("* connected to local websocket server at {}", uri); + ws_stream + } + Err(WebsocketConnectionError::ConnectionNotEstablished) => { + panic!("Error: websocket connection attempt failed, is the Nym client running?") + } + }; + ws_stream + } +} diff --git a/service-providers/sphinx-socks/src/main.rs b/service-providers/network-requester/src/main.rs similarity index 50% rename from service-providers/sphinx-socks/src/main.rs rename to service-providers/network-requester/src/main.rs index 3082577fcb..75f7457065 100644 --- a/service-providers/sphinx-socks/src/main.rs +++ b/service-providers/network-requester/src/main.rs @@ -1,14 +1,36 @@ +use clap::{App, Arg, ArgMatches}; + mod allowed_hosts; mod connection; mod core; mod websocket; +const OPEN_PROXY_ARG: &str = "open-proxy"; + +fn parse_args<'a>() -> ArgMatches<'a> { + App::new("Nym Network Requester") + .author("Nymtech") + .arg( + Arg::with_name(OPEN_PROXY_ARG) + .help("specifies whether this network requester should run in 'open-proxy' mode") + .long(OPEN_PROXY_ARG) + .short("o"), + ) + .get_matches() +} + #[tokio::main] async fn main() { setup_logging(); + let matches = parse_args(); + let open_proxy = matches.is_present(OPEN_PROXY_ARG); + if open_proxy { + println!("\n\nYOU HAVE STARTED IN 'OPEN PROXY' MODE. ANYONE WITH YOUR CLIENT ADDRESS CAN MAKE REQUESTS FROM YOUR MACHINE. PLEASE QUIT IF YOU DON'T UNDERSTAND WHAT YOU'RE DOING.\n\n"); + } + let uri = "ws://localhost:1977"; println!("Starting socks5 service provider:"); - let mut server = core::ServiceProvider::new(uri.into()); + let mut server = core::ServiceProvider::new(uri.into(), open_proxy); server.run().await; } diff --git a/service-providers/sphinx-socks/src/websocket.rs b/service-providers/network-requester/src/websocket.rs similarity index 100% rename from service-providers/sphinx-socks/src/websocket.rs rename to service-providers/network-requester/src/websocket.rs diff --git a/service-providers/sphinx-socks/src/core.rs b/service-providers/sphinx-socks/src/core.rs deleted file mode 100644 index ec9fb69be8..0000000000 --- a/service-providers/sphinx-socks/src/core.rs +++ /dev/null @@ -1,205 +0,0 @@ -use crate::allowed_hosts::{HostsStore, OutboundRequestFilter}; -use crate::connection::Connection; -use crate::websocket; -use futures::channel::mpsc; -use futures::stream::SplitSink; -use futures::{SinkExt, StreamExt}; -use log::*; -use nymsphinx::addressing::clients::Recipient; -use ordered_buffer::OrderedMessageBuffer; -use proxy_helpers::connection_controller::{Controller, ControllerCommand}; -use socks5_requests::{Request, Response}; -use std::path::PathBuf; -use tokio::net::TcpStream; -use tokio_tungstenite::tungstenite::protocol::Message; -use tokio_tungstenite::WebSocketStream; -use websocket::WebsocketConnectionError; -use websocket_requests::{requests::ClientRequest, responses::ServerResponse}; - -pub struct ServiceProvider { - listening_address: String, - outbound_request_filter: OutboundRequestFilter, -} - -impl ServiceProvider { - pub fn new(listening_address: String) -> ServiceProvider { - let allowed_hosts = HostsStore::new( - HostsStore::default_base_dir(), - PathBuf::from("allowed.list"), - ); - - let unknown_hosts = HostsStore::new( - HostsStore::default_base_dir(), - PathBuf::from("unknown.list"), - ); - let outbound_request_filter = OutboundRequestFilter::new(allowed_hosts, unknown_hosts); - ServiceProvider { - listening_address, - outbound_request_filter, - } - } - - /// Listens for any messages from `mix_reader` that should be written back to the mix network - /// via the `websocket_writer`. - async fn mixnet_response_listener( - mut websocket_writer: SplitSink, Message>, - mut mix_reader: mpsc::UnboundedReceiver<(Response, Recipient)>, - ) { - // TODO: wire SURBs in here once they're available - while let Some((response, return_address)) = mix_reader.next().await { - // make 'request' to native-websocket client - let response_message = ClientRequest::Send { - recipient: return_address, - message: response.into_bytes(), - with_reply_surb: false, - }; - - let message = Message::Binary(response_message.serialize()); - websocket_writer.send(message).await.unwrap(); - } - } - - /// Start all subsystems - pub async fn run(&mut self) { - let websocket_stream = self.connect_websocket(&self.listening_address).await; - - // split the websocket so that we could read and write from separate threads - let (websocket_writer, mut websocket_reader) = websocket_stream.split(); - - // channels responsible for managing messages that are to be sent to the mix network. The receiver is - // going to be used by `mixnet_response_listener` - let (mix_input_sender, mix_input_receiver) = mpsc::unbounded::<(Response, Recipient)>(); - - // controller for managing all active connections - let (mut active_connections_controller, controller_sender) = Controller::new(); - tokio::spawn(async move { - active_connections_controller.run().await; - }); - - // start the listener for mix messages - tokio::spawn(async move { - Self::mixnet_response_listener(websocket_writer, mix_input_receiver).await; - }); - - println!("\nAll systems go. Press CTRL-C to stop the server."); - - // for each incoming message from the websocket... (which in 99.99% cases is going to be a mix message) - while let Some(msg) = websocket_reader.next().await { - let data = msg - .expect("we failed to read from the websocket!") - .into_data(); - - // try to recover the actual message from the mix network... - let deserialized_message = match ServerResponse::deserialize(&data) { - Ok(deserialized) => deserialized, - Err(err) => { - error!( - "Failed to deserialize received websocket message! - {}", - err - ); - continue; - } - }; - - let received = match deserialized_message { - ServerResponse::Received(received) => received, - ServerResponse::Error(err) => { - panic!("received error from native client! - {}", err) - } - _ => unimplemented!("probably should never be reached?"), - }; - - let raw_message = received.message; - // TODO: here be potential SURB (i.e. received.reply_SURB) - - // try to treat each received mix message as a service provider request - let deserialized_request = match Request::try_from_bytes(&raw_message) { - Ok(request) => request, - Err(err) => { - error!("Failed to deserialized received request! - {}", err); - continue; - } - }; - - match deserialized_request { - Request::Connect { - conn_id, - remote_addr, - message, - return_address, - } => { - if !self.outbound_request_filter.check(&remote_addr) { - log::info!("Domain {:?} failed filter check", remote_addr); - continue; - } - - let controller_sender_clone = controller_sender.clone(); - let mut ordered_buffer = OrderedMessageBuffer::new(); - ordered_buffer.write(message); - let init_data = ordered_buffer - .read() - .expect("we received connect request but it wasn't sequence 0!"); - - // and start the proxy for this connection - let mix_input_sender_clone = mix_input_sender.clone(); - tokio::spawn(async move { - let mut conn = match Connection::new( - conn_id, - remote_addr.clone(), - &init_data, - return_address, - ) - .await - { - Ok(conn) => conn, - Err(err) => { - error!( - "error while connecting to {:?} ! - {:?}", - remote_addr.clone(), - err - ); - return; - } - }; - - // Connect implies it's a fresh connection - register it with our controller - let (mix_sender, mix_receiver) = mpsc::unbounded(); - controller_sender_clone - .unbounded_send(ControllerCommand::Insert( - conn_id, - mix_sender, - ordered_buffer, - )) - .unwrap(); - - info!("Starting proxy for {}", remote_addr.clone()); - conn.run_proxy(mix_receiver, mix_input_sender_clone).await; - // proxy is done - remove the access channel from the controller - controller_sender_clone - .unbounded_send(ControllerCommand::Remove(conn_id)) - .unwrap(); - info!("Proxy for {} is finished", remote_addr); - }); - } - // on send just tell the controller to send that data to the correct connection - Request::Send(conn_id, data, closed) => controller_sender - .unbounded_send(ControllerCommand::Send(conn_id, data, closed)) - .unwrap(), - } - } - } - - // Make the websocket connection so we can receive incoming Mixnet messages. - async fn connect_websocket(&self, uri: &str) -> WebSocketStream { - let ws_stream = match websocket::Connection::new(uri).connect().await { - Ok(ws_stream) => { - info!("* connected to local websocket server at {}", uri); - ws_stream - } - Err(WebsocketConnectionError::ConnectionNotEstablished) => { - panic!("Error: websocket connection attempt failed, is the Nym client running?") - } - }; - return ws_stream; - } -} diff --git a/validator/Cargo.toml b/validator/Cargo.toml deleted file mode 100644 index 7d5829c00c..0000000000 --- a/validator/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -build = "build.rs" -name = "nym-validator" -version = "0.8.1" -authors = ["Dave Hrycyszyn ", "Jedrzej Stuczynski "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -abci = "0.6.4" -bodyparser = "0.8.0" -byteorder = "1.3.2" -clap = "2.33.0" -dirs = "2.0.2" -dotenv = "0.15.0" -futures = "0.3.1" -iron = "0.6.1" -log = "0.4" -pretty_env_logger = "0.3" -router = "0.6.0" -serde = "1.0.104" -serde_json = "1.0.48" -tokio = { version = "0.2", features = ["full"] } - -## internal -config = { path = "../common/config" } - -[build-dependencies] -built = "0.4.3" - -[dev-dependencies] -tempfile = "3.1.0" \ No newline at end of file diff --git a/validator/README.md b/validator/README.md deleted file mode 100644 index 47b90bec59..0000000000 --- a/validator/README.md +++ /dev/null @@ -1,21 +0,0 @@ -Nym Validator -============= - -The Nym Validator has several jobs: - -* use Tendermint (v0.33.0) to maintain a total global ordering of incoming transactions -* rewards + stake slashing based quality of service measurements for mixnet nodes (aka "mixmining") -* generate Coconut credentials and ensure they're not double spent -* maintain a decentralized directory of all Nym nodes that have staked into the system - -Some of these functions may be moved away to their own node types in the future, for example to increase scalability or performance. At the moment, we'd like to keep deployments simple, so they're all in the validator node. - -Running the validator on your local machine -------------------------------------------- - -1. Download and install [Tendermint 0.32.7](https://github.com/tendermint/tendermint/releases/tag/v0.32.7) -2. `tendermint init` sets up Tendermint for use -3. `tendermint node` runs Tendermint. You'll get errors until you run the Nym validator, this is normal :). -4. `cp sample-configs/validator-config.toml.sample sample-configs/validator-config.toml` -5. `cargo run -- run --config ../sample-configs/validator-config.toml` builds the Nym Validator and runs it - diff --git a/validator/build.rs b/validator/build.rs deleted file mode 100644 index 56d753472c..0000000000 --- a/validator/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use built; - -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/validator/diesel.toml b/validator/diesel.toml deleted file mode 100644 index 92267c829f..0000000000 --- a/validator/diesel.toml +++ /dev/null @@ -1,5 +0,0 @@ -# For documentation on how to configure this file, -# see diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/schema.rs" diff --git a/validator/src/built_info.rs b/validator/src/built_info.rs deleted file mode 100644 index 38b46d59e6..0000000000 --- a/validator/src/built_info.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/validator/src/commands/init.rs b/validator/src/commands/init.rs deleted file mode 100644 index d3efa04064..0000000000 --- a/validator/src/commands/init.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::commands::override_config; -use clap::{App, Arg, ArgMatches}; -use config::NymConfig; - -pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { - App::new("init") - .about("Initialise the validator") - .arg( - Arg::with_name("id") - .long("id") - .help("Id of the nym-validator we want to create config for.") - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name("location") - .long("location") - .help("Optional geographical location of this node") - .takes_value(true), - ) - .arg( - Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the validator is sending presence to and uses for mix mining") - .takes_value(true), - ) -} - -pub fn execute(matches: &ArgMatches) { - let id = matches.value_of("id").unwrap(); - println!("Initialising validator {}...", id); - - let mut config = crate::config::Config::new(id); - - config = override_config(config, matches); - - let config_save_location = config.get_config_file_save_location(); - config - .save_to_file(None) - .expect("Failed to save the config file"); - println!("Saved configuration file to {:?}", config_save_location); - - println!("Validator configuration completed.\n\n\n") -} diff --git a/validator/src/commands/mod.rs b/validator/src/commands/mod.rs deleted file mode 100644 index 01b11b3a47..0000000000 --- a/validator/src/commands/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::config::Config; -use clap::ArgMatches; - -pub mod init; -pub mod run; - -pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { - if let Some(directory) = matches.value_of("directory") { - config = config.with_custom_directory(directory); - } - - if let Some(location) = matches.value_of("location") { - config = config.with_location(location); - } - - config -} diff --git a/validator/src/commands/run.rs b/validator/src/commands/run.rs deleted file mode 100644 index cc636163e3..0000000000 --- a/validator/src/commands/run.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Commenting config out temporarily, we'll undoubtedly need it back soon. -// use crate::commands::override_config; -// use crate::config::Config; -// use config::NymConfig; -use crate::validator::Validator; -use clap::{App, Arg, ArgMatches}; - -pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { - App::new("run") - .about("Starts the validator") - .arg( - Arg::with_name("id") - .long("id") - .help("Id of the nym-validator we want to run") - .takes_value(true) - .required(true), - ) - // the rest of arguments are optional, they are used to override settings in config file - .arg( - Arg::with_name("location") - .long("location") - .help("Optional geographical location of this node") - .takes_value(true), - ) - .arg( - Arg::with_name("config") - .long("config") - .help("Custom path to the nym-validator configuration file") - .takes_value(true), - ) - .arg( - Arg::with_name("directory") - .long("directory") - .help("Address of the directory server the validator is sending presence to and uses for mix mining") - .takes_value(true), - ) -} - -pub fn execute(matches: &ArgMatches) { - let id = matches.value_of("id").unwrap(); - - println!("Starting validator {}...", id); - - // let mut config = - // Config::load_from_file(matches.value_of("config").map(|path| path.into()), Some(id)) - // .expect("Failed to load config file"); - - // config = override_config(config, matches); - - let validator = Validator::new(); - validator.start() -} diff --git a/validator/src/config/mod.rs b/validator/src/config/mod.rs deleted file mode 100644 index 1bd21771ea..0000000000 --- a/validator/src/config/mod.rs +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::config::template::config_template; -use config::NymConfig; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::time; - -mod template; - -// where applicable, the below are defined in milliseconds -const DEFAULT_DIRECTORY_SERVER: &str = "https://directory.nymtech.net"; - -// 'MIXMINING' -const DEFAULT_MIX_MINING_DELAY: u64 = 10_000; -const DEFAULT_MIX_MINING_RESOLUTION_TIMEOUT: u64 = 5_000; -const DEFAULT_MIX_MINING_CONNECTION_TIMEOUT: u64 = 1_500; - -const DEFAULT_NUMBER_OF_MIX_MINING_TEST_PACKETS: u64 = 2; - -// 'DEBUG' -const DEFAULT_PRESENCE_SENDING_DELAY: u64 = 3000; - -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - validator: Validator, - - mix_mining: MixMining, - - tendermint: Tendermint, - - #[serde(default)] - logging: Logging, - #[serde(default)] - debug: Debug, -} - -impl NymConfig for Config { - fn template() -> &'static str { - config_template() - } - - fn config_file_name() -> String { - "config.toml".to_string() - } - - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("validators") - } - - fn root_directory(&self) -> PathBuf { - self.validator.nym_root_directory.clone() - } - - fn config_directory(&self) -> PathBuf { - self.validator - .nym_root_directory - .join(&self.validator.id) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.validator - .nym_root_directory - .join(&self.validator.id) - .join("data") - } -} - -impl Config { - pub fn new>(id: S) -> Self { - Config::default().with_id(id) - } - - // builder methods - pub fn with_id>(mut self, id: S) -> Self { - let id = id.into(); - - // calls to any defaults requiring id (see: client, mixnode, provider): - - self.validator.id = id; - self - } - - pub fn with_custom_directory>(mut self, directory_server: S) -> Self { - let directory_server_string = directory_server.into(); - self.debug.presence_directory_server = directory_server_string.clone(); - self.mix_mining.directory_server = directory_server_string; - self - } - - pub fn with_location>(mut self, location: S) -> Self { - self.validator.location = location.into(); - self - } - - // getters - pub fn get_config_file_save_location(&self) -> PathBuf { - self.config_directory().join(Self::config_file_name()) - } - - #[allow(dead_code)] - pub fn get_location(&self) -> String { - self.validator.location.clone() - } - - // dead_code until validator actually sends the presence data - #[allow(dead_code)] - pub fn get_presence_directory_server(&self) -> String { - self.debug.presence_directory_server.clone() - } - - #[allow(dead_code)] - pub fn get_presence_sending_delay(&self) -> time::Duration { - time::Duration::from_millis(self.debug.presence_sending_delay) - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Validator { - /// ID specifies the human readable ID of this particular validator. - id: String, - - /// Completely optional value specifying geographical location of this particular node. - /// Currently it's used entirely for debug purposes, as there are no mechanisms implemented - /// to verify correctness of the information provided. However, feel free to fill in - /// this field with as much accuracy as you wish to share. - location: String, - - /// nym_home_directory specifies absolute path to the home nym MixNodes directory. - /// It is expected to use default value and hence .toml file should not redefine this field. - nym_root_directory: PathBuf, -} - -impl Validator { - fn default_location() -> String { - "unknown".into() - } -} - -impl Default for Validator { - fn default() -> Self { - Validator { - id: "".to_string(), - location: Self::default_location(), - nym_root_directory: Config::default_root_directory(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct MixMining { - /// Directory server from which the validator will obtain initial topology. - directory_server: String, - - /// The uniform delay every which validator are running their mix-mining procedure. - /// The provided value is interpreted as milliseconds. - run_delay: u64, - - /// During the mix-mining process, test packets are sent through various network - /// paths. This timeout determines waiting period until it is decided that the packet - /// did not reach its destination. - /// The provided value is interpreted as milliseconds. - resolution_timeout: u64, - - /// Timeout for trying to establish connection to node endpoints. - /// The provided value is interpreted as milliseconds. - connection_timeout: u64, - - /// How many packets should be sent through each path during the mix-mining procedure. - number_of_test_packets: u64, -} - -impl Default for MixMining { - fn default() -> Self { - MixMining { - directory_server: "https://directory.nymtech.net".to_string(), - run_delay: DEFAULT_MIX_MINING_DELAY, - resolution_timeout: DEFAULT_MIX_MINING_RESOLUTION_TIMEOUT, - number_of_test_packets: DEFAULT_NUMBER_OF_MIX_MINING_TEST_PACKETS, - connection_timeout: DEFAULT_MIX_MINING_CONNECTION_TIMEOUT, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Tendermint {} - -impl Default for Tendermint { - fn default() -> Self { - Tendermint {} - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Logging {} - -impl Default for Logging { - fn default() -> Self { - Logging {} - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct Debug { - /// Directory server to which the server will be reporting their presence data. - presence_directory_server: String, - - /// Delay between each subsequent presence data being sent. - presence_sending_delay: u64, -} - -impl Debug {} - -impl Default for Debug { - fn default() -> Self { - Debug { - presence_directory_server: DEFAULT_DIRECTORY_SERVER.to_string(), - presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, - } - } -} - -#[cfg(test)] -mod validator_config { - use super::*; - - #[test] - fn after_saving_default_config_the_loaded_one_is_identical() { - // need to figure out how to do something similar but without touching the disk - // or the file system at all... - let temp_location = tempfile::tempdir().unwrap().path().join("config.toml"); - let default_config = Config::default().with_id("foomp".to_string()); - default_config - .save_to_file(Some(temp_location.clone())) - .unwrap(); - - let loaded_config = Config::load_from_file(Some(temp_location), None).unwrap(); - - assert_eq!(default_config, loaded_config); - } -} diff --git a/validator/src/config/template.rs b/validator/src/config/template.rs deleted file mode 100644 index 7525dc7a8c..0000000000 --- a/validator/src/config/template.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub(crate) fn config_template() -> &'static str { - // While using normal toml marshalling would have been way simpler with less overhead, - // I think it's useful to have comments attached to the saved config file to explain behaviour of - // particular fields. - // Note: any changes to the template must be reflected in the appropriate structs in mod.rs. - r#" -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -##### main base mixnode config options ##### - -[validator] -# Human readable ID of this particular validator. -id = '{{ validator.id }}' - -# Completely optional value specifying geographical location of this particular node. -# Currently it's used entirely for debug purposes, as there are no mechanisms implemented -# to verify correctness of the information provided. However, feel free to fill in -# this field with as much accuracy as you wish to share. -location = '{{ validator.location }}' - -##### advanced configuration options ##### - -# nym_home_directory specifies absolute path to the home nym validators directory. -# It is expected to use default value and hence .toml file should not redefine this field. -nym_root_directory = '{{ validator.nym_root_directory }}' - - -##### mix mining config options ##### - -[mix_mining] - -# Directory server from which the validator will obtain initial topology. -directory_server = '{{ mix_mining.directory_server }}' - -# The uniform delay every which validator are running their mix-mining procedure. -# The provided value is interpreted as milliseconds. -run_delay = {{ mix_mining.run_delay }} - -# During the mix-mining process, test packets are sent through various network -# paths. This timeout determines waiting period until it is decided that the packet -# did not reach its destination. -# The provided value is interpreted as milliseconds. -resolution_timeout = {{ mix_mining.resolution_timeout }} - -# Timeout for trying to establish connection to node endpoints. -# The provided value is interpreted as milliseconds. -connection_timeout = {{ mix_mining.connection_timeout }} - -# How many packets should be sent through each path during the mix-mining procedure. -number_of_test_packets = {{ mix_mining.number_of_test_packets }} - - -##### tendermint config options ##### - -[tendermint] - - - -##### logging configuration options ##### - -[logging] - -# TODO - -"# -} diff --git a/validator/src/main.rs b/validator/src/main.rs deleted file mode 100644 index 4ce74cc169..0000000000 --- a/validator/src/main.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use clap::{App, ArgMatches}; - -pub mod built_info; -mod commands; -mod config; -mod network; -mod services; -mod validator; - -fn main() { - dotenv::dotenv().ok(); - setup_logging(); - println!("{}", banner()); - - let arg_matches = App::new("Nym Validator") - .version(built_info::PKG_VERSION) - .author("Nymtech") - .about("Implementation of Nym Validator") - .subcommand(commands::init::command_args()) - .subcommand(commands::run::command_args()) - .get_matches(); - - execute(arg_matches); -} - -fn execute(matches: ArgMatches) { - match matches.subcommand() { - ("init", Some(m)) => commands::init::execute(m), - ("run", Some(m)) => commands::run::execute(m), - _ => println!("{}", usage()), - } -} - -fn usage() -> &'static str { - "usage: --help to see available options.\n\n" -} - -fn banner() -> String { - format!( - r#" - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (validator - version {:}) - - "#, - built_info::PKG_VERSION - ) -} - -fn setup_logging() { - let mut log_builder = pretty_env_logger::formatted_timed_builder(); - if let Ok(s) = ::std::env::var("RUST_LOG") { - log_builder.parse_filters(&s); - } else { - // default to 'Info' - log_builder.filter(None, log::LevelFilter::Info); - } - - log_builder - .filter_module("hyper", log::LevelFilter::Warn) - .filter_module("tokio_reactor", log::LevelFilter::Warn) - .filter_module("reqwest", log::LevelFilter::Warn) - .filter_module("mio", log::LevelFilter::Warn) - .filter_module("want", log::LevelFilter::Warn) - .init(); -} diff --git a/validator/src/network/mod.rs b/validator/src/network/mod.rs deleted file mode 100644 index 1084c25e7a..0000000000 --- a/validator/src/network/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! The `network` module provides interfaces to external systems via network -//! connectivity. -//! -pub mod rest; -pub mod tendermint; diff --git a/validator/src/network/rest/capacity/mod.rs b/validator/src/network/rest/capacity/mod.rs deleted file mode 100644 index 6813023401..0000000000 --- a/validator/src/network/rest/capacity/mod.rs +++ /dev/null @@ -1,59 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::*; -use bodyparser::Struct; -use iron::mime::Mime; -use iron::status; -use iron::Handler; - -/// Holds data for a capacity update (json) -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Capacity { - value: usize, -} - -pub struct Update { - service: Arc>, -} - -impl Update { - pub fn new(service: Arc>) -> Update { - Update { service } - } -} - -impl Handler for Update { - fn handle(&self, req: &mut Request) -> IronResult { - let json_parse = req.get::>(); - - match json_parse { - Ok(capacity) => { - let capacity = capacity.expect("Unexpected JSON parsing problem").value; - self.service.lock().unwrap().set_capacity(capacity); - Ok(Response::with(status::Created)) - } - Err(err) => Ok(Response::with((status::BadRequest, err.detail))), - } - } -} - -pub struct Get { - service: Arc>, -} - -impl Get { - pub fn new(service: Arc>) -> Get { - Get { service } - } -} - -impl Handler for Get { - fn handle(&self, _: &mut Request) -> IronResult { - let content_type = "application/json".parse::().unwrap(); - let value = self.service.lock().unwrap().capacity(); - let c = Capacity { value }; - let json = serde_json::to_string(&c).unwrap(); - Ok(Response::with((content_type, status::Ok, json))) - } -} diff --git a/validator/src/network/rest/mod.rs b/validator/src/network/rest/mod.rs deleted file mode 100644 index 4b991caf00..0000000000 --- a/validator/src/network/rest/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use crate::services::mixmining; -use iron::prelude::*; -use presence::mixnode; -use presence::topology; -use router::Router; -use std::sync::{Arc, Mutex}; - -mod capacity; -mod presence; -mod staking; - -pub struct Api { - mixmining_service: Arc>, -} - -impl Api { - pub fn new(mixmining_service: mixmining::Service) -> Api { - let service = Arc::new(Mutex::new(mixmining_service)); - Api { - mixmining_service: service, - } - } - - /// Run the REST API. - pub async fn run(self) { - let port = 3000; // TODO: make this configurable - let address = format!("localhost:{}", port); - println!("* starting REST API on http://{}", address); - - let router = self.setup_router(); - - Iron::new(router).http(address).unwrap(); - } - - /// Tie together URL route paths with handler functions. - fn setup_router(self) -> Router { - // define a Router to hold our routes - let mut router = Router::new(); - - // set up handlers - let capacity_update = capacity::Update::new(Arc::clone(&self.mixmining_service)); - let capacity_get = capacity::Get::new(Arc::clone(&self.mixmining_service)); - let presence_mixnode_create = - mixnode::CreatePresence::new(Arc::clone(&self.mixmining_service)); - let topology_get = topology::GetTopology::new(Arc::clone(&self.mixmining_service)); - - // tie routes to handlers - router.get("/capacity", capacity_get, "capacity_get"); - router.post("/capacity", capacity_update, "capacity_update"); - router.get("/topology", topology_get, "topology_get"); - router.post( - "/presence/mixnodes", - presence_mixnode_create, - "presence_mixnodes_post", - ); - - router - } -} diff --git a/validator/src/network/rest/models/presence.rs b/validator/src/network/rest/models/presence.rs deleted file mode 100644 index a2a781a236..0000000000 --- a/validator/src/network/rest/models/presence.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use serde::{Deserialize, Serialize}; - -// Topology shows us the current state of the overall Nym network -#[derive(Serialize, Deserialize, Debug)] -pub struct Topology { - pub validators: Vec, - pub mix_nodes: Vec, - pub service_providers: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Validator { - host: String, - public_key: String, - version: String, - last_seen: u64, - location: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct MixNode { - host: String, - public_key: String, - version: String, - last_seen: u64, - location: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ServiceProvider { - host: String, - public_key: String, - version: String, - last_seen: u64, - location: String, -} diff --git a/validator/src/network/rest/presence/conversions.rs b/validator/src/network/rest/presence/conversions.rs deleted file mode 100644 index eae70296cf..0000000000 --- a/validator/src/network/rest/presence/conversions.rs +++ /dev/null @@ -1,135 +0,0 @@ -use super::models::Timestamp; -use crate::network::rest::presence::models::Mixnode as RestMixnode; -use crate::network::rest::presence::models::Topology as RestTopology; -use crate::services::mixmining::models::Mixnode as ServiceMixnode; -use crate::services::mixmining::models::Topology as ServiceTopology; -use std::convert::From; - -impl ServiceMixnode { - pub fn from_rest_mixnode_with_timestamp( - rest_mix: RestMixnode, - timestamp: Timestamp, - ) -> ServiceMixnode { - ServiceMixnode { - host: rest_mix.host, - last_seen: timestamp.into(), - location: rest_mix.location, - public_key: rest_mix.public_key, - stake: 0, - version: rest_mix.version, - } - } -} - -impl From for RestMixnode { - fn from(value: ServiceMixnode) -> RestMixnode { - RestMixnode { - host: value.host, - location: value.location, - public_key: value.public_key, - version: value.version, - } - } -} - -impl ServiceTopology { - #[cfg(test)] // un-testify this when you need it for real code, this kills warning - pub fn from_rest_topology_with_timestamp( - rest_topology: RestTopology, - timestamp: Timestamp, - ) -> ServiceTopology { - let mut converted_mixnodes: Vec = Vec::new(); - for mixnode in rest_topology.mixnodes { - converted_mixnodes.push(ServiceMixnode::from_rest_mixnode_with_timestamp( - mixnode, timestamp, - )); - } - ServiceTopology { - mixnodes: converted_mixnodes.to_vec(), - service_providers: vec![], // add these when conversions exist - validators: vec![], // add these when conversions exist - } - } -} - -impl From for RestTopology { - fn from(value: ServiceTopology) -> RestTopology { - let mut converted_mixnodes: Vec = Vec::new(); - for mixnode in value.mixnodes { - converted_mixnodes.push(mixnode.into()); - } - RestTopology { - mixnodes: converted_mixnodes.to_vec(), - service_providers: vec![], // add these when conversions exist - validators: vec![], // add these when conversions exist - } - } -} - -#[cfg(test)] -mod test_presence_conversions_for_mixmining_service { - fn rest_mixnode_fixture() -> RestMixnode { - RestMixnode { - host: "foo.org".to_owned(), - public_key: "abc".to_owned(), - location: "London".to_owned(), - version: "1.0.0".to_owned(), - } - } - - fn service_mixnode_fixture() -> ServiceMixnode { - ServiceMixnode { - host: "foo.org".to_owned(), - public_key: "abc".to_owned(), - last_seen: 1234, - location: "London".to_owned(), - stake: 0, - version: "1.0.0".to_owned(), - } - } - - use super::*; - - #[test] - fn test_building_service_mixnode_from_rest_mixnode() { - let rest_mixnode = rest_mixnode_fixture(); - let timestamp = Timestamp::default(); - let service_mixnode = - ServiceMixnode::from_rest_mixnode_with_timestamp(rest_mixnode.clone(), timestamp); - assert_eq!(service_mixnode.host, rest_mixnode.host); - assert_eq!(service_mixnode.public_key, rest_mixnode.public_key); - assert_eq!(service_mixnode.location, rest_mixnode.location); - assert_eq!(service_mixnode.stake, 0); - assert_eq!(service_mixnode.version, rest_mixnode.version); - // I'm not going to test the last_seen timestamp as I can't be bothered - // setting up a fake clock right now. - // The behaviour is: it should set time to SystemTime::now(). - } - - #[test] - fn test_building_rest_mixnode_from_service_mixnode() { - let service_mixnode = service_mixnode_fixture(); - let rest_mixnode = RestMixnode::from(service_mixnode.clone()); - assert_eq!(rest_mixnode.host, service_mixnode.host); - assert_eq!(rest_mixnode.public_key, service_mixnode.public_key); - assert_eq!(rest_mixnode.location, service_mixnode.location); - assert_eq!(rest_mixnode.version, service_mixnode.version); - } - - #[test] - fn test_building_service_topology_from_rest_topology() { - let rest_mixnode = rest_mixnode_fixture(); - let rest_topology = RestTopology { - mixnodes: vec![rest_mixnode.clone()], - service_providers: vec![], - validators: vec![], - }; - - let timestamp = Timestamp::default(); - let service_topology = - ServiceTopology::from_rest_topology_with_timestamp(rest_topology, timestamp); - let service_mixnode = - ServiceMixnode::from_rest_mixnode_with_timestamp(rest_mixnode, timestamp); - assert_eq!(service_mixnode, service_topology.mixnodes[0]); - } -} diff --git a/validator/src/network/rest/presence/mixnode.rs b/validator/src/network/rest/presence/mixnode.rs deleted file mode 100644 index aa57f6d2fa..0000000000 --- a/validator/src/network/rest/presence/mixnode.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::*; -use crate::network::rest::presence::models::Mixnode as PresenceMixnode; -use crate::services::mixmining::models::Mixnode as ServiceMixnode; -use bodyparser::Struct; -use iron::status; -use iron::Handler; -use models::Timestamp; - -pub struct CreatePresence { - service: Arc>, -} - -impl CreatePresence { - pub fn new(service: Arc>) -> CreatePresence { - CreatePresence { service } - } -} - -impl Handler for CreatePresence { - fn handle(&self, req: &mut Request) -> IronResult { - let json_parse = req.get::>(); - - match json_parse { - Ok(mixnode) => { - let mixnode = mixnode.expect("Unexpected JSON parsing problem"); - self.service - .lock() - .unwrap() - .add(ServiceMixnode::from_rest_mixnode_with_timestamp( - mixnode, - Timestamp::default(), - )); - Ok(Response::with(status::Created)) - } - Err(err) => Ok(Response::with((status::BadRequest, err.detail))), - } - } -} diff --git a/validator/src/network/rest/presence/mod.rs b/validator/src/network/rest/presence/mod.rs deleted file mode 100644 index 108d75277e..0000000000 --- a/validator/src/network/rest/presence/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -use super::*; - -mod conversions; -pub mod mixnode; -mod models; -pub mod topology; diff --git a/validator/src/network/rest/presence/models.rs b/validator/src/network/rest/presence/models.rs deleted file mode 100644 index b4efde2808..0000000000 --- a/validator/src/network/rest/presence/models.rs +++ /dev/null @@ -1,66 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::time::{SystemTime, UNIX_EPOCH}; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Mixnode { - pub host: String, - pub public_key: String, - pub version: String, - pub location: String, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct ServiceProvider { - host: String, - public_key: String, - version: String, - last_seen: u64, - location: String, -} - -/// Topology shows us the current state of the overall Nym network -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Topology { - pub mixnodes: Vec, - pub service_providers: Vec, - pub validators: Vec, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Validator { - host: String, - public_key: String, - version: String, - last_seen: u64, - location: String, -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)] -pub struct Timestamp(u64); - -impl Default for Timestamp { - fn default() -> Timestamp { - Timestamp( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64, - ) - } -} - -impl From for Timestamp { - fn from(v: u64) -> Timestamp { - Timestamp(v) - } -} - -impl Into for Timestamp { - fn into(self) -> u64 { - self.0 - } -} diff --git a/validator/src/network/rest/presence/topology.rs b/validator/src/network/rest/presence/topology.rs deleted file mode 100644 index 46517c62f7..0000000000 --- a/validator/src/network/rest/presence/topology.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::*; -use iron::status; -use iron::Handler; - -pub struct GetTopology { - service: Arc>, -} - -impl GetTopology { - pub fn new(service: Arc>) -> GetTopology { - GetTopology { service } - } -} - -impl Handler for GetTopology { - fn handle(&self, _req: &mut Request) -> IronResult { - println!("Getting topology!..."); - let service_topology = self.service.lock().unwrap().topology(); - let topology = models::Topology::from(service_topology); - let response = serde_json::to_string_pretty(&topology).unwrap(); - Ok(Response::with((status::Ok, response))) - } -} diff --git a/validator/src/network/rest/routes/topology.rs b/validator/src/network/rest/routes/topology.rs deleted file mode 100644 index d2783cbfc7..0000000000 --- a/validator/src/network/rest/routes/topology.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; -use crate::network::rest::models::presence::{MixNode, ServiceProvider, Topology, Validator}; - -pub fn get(_req: &mut Request) -> IronResult { - let topology = Topology { - mix_nodes: Vec::::new(), - service_providers: Vec::::new(), - validators: Vec::::new(), - }; - let response = serde_json::to_string_pretty(&topology).unwrap(); - Ok(Response::with((status::Ok, response))) -} diff --git a/validator/src/network/rest/staking/mod.rs b/validator/src/network/rest/staking/mod.rs deleted file mode 100644 index b4780ebde8..0000000000 --- a/validator/src/network/rest/staking/mod.rs +++ /dev/null @@ -1 +0,0 @@ -// pub struct StakeUpdate {} diff --git a/validator/src/network/tendermint/mod.rs b/validator/src/network/tendermint/mod.rs deleted file mode 100644 index 140a8153be..0000000000 --- a/validator/src/network/tendermint/mod.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use abci::*; -use byteorder::{BigEndian, ByteOrder}; - -// Convert incoming tx network data to the proper BigEndian size. txs.len() > 8 will return 0 -fn convert_tx(tx: &[u8]) -> u64 { - if tx.len() < 8 { - let pad = 8 - tx.len(); - let mut x = vec![0; pad]; - x.extend_from_slice(tx); - return BigEndian::read_u64(x.as_slice()); - } - BigEndian::read_u64(tx) -} - -pub struct Abci { - count: u64, -} - -impl Abci { - pub fn new() -> Abci { - Abci { count: 0 } - } - - pub async fn run(self) { - println!("* starting Tendermint abci"); - abci::run_local(self); - } -} - -impl abci::Application for Abci { - // Validate transactions. Rule: Transactions must be incremental: 1,2,3,4... - fn check_tx(&mut self, req: &RequestCheckTx) -> ResponseCheckTx { - // Get the Tx [u8] and convert to u64 - let c = convert_tx(req.get_tx()); - let mut response = ResponseCheckTx::new(); - - // Validation logic - if c != self.count + 1 { - response.set_code(1); - response.set_log(String::from("Count must be incremental!")); - return response; - } - - // Update state to keep state correct for next check_tx call - self.count = c; - response - } - - fn deliver_tx(&mut self, req: &RequestDeliverTx) -> ResponseDeliverTx { - // Get the Tx [u8] - let c = convert_tx(req.get_tx()); - // Update state - self.count = c; - // Return default code 0 == bueno - ResponseDeliverTx::new() - } - - fn commit(&mut self, _req: &RequestCommit) -> ResponseCommit { - // Create the response - let mut response = ResponseCommit::new(); - // Convert count to bits - let mut buf = [0; 8]; - BigEndian::write_u64(&mut buf, self.count); - // Set data so last state is included in the block - response.set_data(buf.to_vec()); - response - } -} diff --git a/validator/src/schema.rs b/validator/src/schema.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/validator/src/schema.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/validator/src/services/mixmining/db.rs b/validator/src/services/mixmining/db.rs deleted file mode 100644 index c53a2e19f0..0000000000 --- a/validator/src/services/mixmining/db.rs +++ /dev/null @@ -1,105 +0,0 @@ -use super::Mixnode; - -/// A (currently RAM-based) data store to keep tabs on which nodes have what -/// stake assigned to them. -#[derive(Clone, Debug, PartialEq)] -pub struct MixminingDb { - mixnodes: Vec, - capacity: usize, -} - -impl MixminingDb { - pub fn new() -> MixminingDb { - let mixnodes = Vec::::new(); - MixminingDb { - capacity: 6, - mixnodes, - } - } - - pub fn add(&mut self, mixnode: Mixnode) { - self.mixnodes.push(mixnode); - } - - pub fn get_mixnodes(&self) -> &Vec { - &self.mixnodes - } - - pub fn set_capacity(&mut self, capacity: usize) { - self.capacity = capacity; - } - - pub fn capacity(&self) -> usize { - self.capacity - } -} - -#[cfg(test)] -mod capacity { - use super::*; - - #[test] - fn starts_at_6() { - let db = MixminingDb::new(); - assert_eq!(6, db.capacity()); - } - - #[test] - fn setting_and_getting_work() { - let mut db = MixminingDb::new(); - db.set_capacity(1); - assert_eq!(1, db.capacity()); - } -} - -#[cfg(test)] -mod adding_and_retrieving_mixnodes { - use super::*; - - #[test] - fn add_and_retrieve_one_works() { - let node = fake_mixnode("London, UK"); - let mut db = MixminingDb::new(); - - db.add(node.clone()); - - assert_eq!(&node, db.get_mixnodes().first().unwrap()); - } - - #[test] - fn add_and_retrieve_two_works() { - let node1 = fake_mixnode("London, UK"); - let node2 = fake_mixnode("Neuchatel"); - let mut db = MixminingDb::new(); - - db.add(node1.clone()); - db.add(node2.clone()); - - assert_eq!(node1, db.get_mixnodes()[0]); - assert_eq!(node2, db.get_mixnodes()[1]); - } - - #[test] - fn starts_empty() { - let db = MixminingDb::new(); - assert_eq!(0, db.mixnodes.len()); - } - - #[test] - fn calling_list_when_empty_returns_empty_vec() { - let db = MixminingDb::new(); - let empty: Vec = vec![]; - assert_eq!(&empty, db.get_mixnodes()); - } - - fn fake_mixnode(location: &str) -> Mixnode { - Mixnode { - host: String::from("foo.com"), - last_seen: 123, - location: String::from(location), - public_key: String::from("abc123"), - stake: 8, - version: String::from("1.0"), - } - } -} diff --git a/validator/src/services/mixmining/mod.rs b/validator/src/services/mixmining/mod.rs deleted file mode 100644 index 42af4c865f..0000000000 --- a/validator/src/services/mixmining/mod.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use db::MixminingDb; -use models::*; - -pub mod db; -pub mod models; -mod tests; - -pub struct Service { - db: MixminingDb, -} - -/// The mixmining::Service provides logic for updating and slashing mixnode -/// stake, retrieving lists of mixnodes based on stake, and adding/removing -/// mixnodes from the active set. It monitors mixnodes and rewards or slashes -/// based on the observed quality of service provided by a given mixnode. -/// -/// Mixing and staking interact. Mixnodes first need to announce -/// their presence to the validators. -/// -/// The mixnode then goes into the stack of available mixnodes. -/// -/// However, it's not necessarily going to start actively mixing traffic. -/// That depends on how much stake is riding on it, and how much capacity the -/// network requires right now. We depend on the wisdom of stakers to put their -/// money on trustworthy mixnodes. -/// -/// The active set of mixnodes will be able to expand or contract based on capacity. -/// For now, we simply take the top nodes available, ordered by -/// . -/// -/// A lot is going to need to change here. Commented code is here mainly to -/// quickly sketch out the guts of the mixmining and staking service. This is not the basis -/// of our real staking system quite yet - it's a way to start getting the system -/// to function with all the different node types to start talking to each other, -/// and will be dramatically reworked over the next few months. -impl Service { - pub fn new(db: MixminingDb) -> Service { - Service { db } - } - - // Add a mixnode so that it becomes part of the possible mixnode set. - pub fn add(&mut self, mixnode: Mixnode) { - self.db.add(mixnode); - } - - pub fn topology(&self) -> Topology { - let mixnodes = self.db.get_mixnodes(); - let service_providers: Vec = vec![]; - let validators: Vec = vec![]; - Topology::new(mixnodes.to_vec(), service_providers, validators) - } - - pub fn set_capacity(&mut self, capacity: usize) { - self.db.set_capacity(capacity); - } - - /// A fake capacity, so we can take the top n mixnodes based on stake - pub fn capacity(&self) -> usize { - self.db.capacity() - } - - /* - - /// Update (or create) a given mixnode stake, identified by the mixnode's public key - fn update(&self, public_key: &str, amount: u64) { - // retrieve the given Mixnode from the database and update its stake - } - - /// For now, we have no notion of measuring capacity. For now just use capacity(). - fn active_mixnodes(&self) -> Vec { - Vec::::new() - // hit the database - } - - - /// Remove a mixnode from the active set in a way that does not impact its stake. - /// In a more built-out system, this method would mean: - /// "mixnode x has done its job well and requested to leave, so it can be removed - /// at the end of an epoch." - fn remove(&self, public_key: &str) { - // free locked up stake back to originating stakeholder - // remove the mixnode from the database - } - - /// Add the given amount of stake to the given Mixnode. Presumably it has done - /// its job well. - fn reward(&self, public_key: &str, amount: u64) {} - - /// Slash a mixnode's stake based on bad performance or detected malign intent. - fn slash(&self, public_key: &str, amount: u64) { - // transfer slashed stake amount to reserve fund - // retrieve the mixnode from the database, and decrement its stake amount - // by the amount given. - } - - /// Slash a mixnode's stake and immediately remove it from the mixnode set. - fn slash_remove(&self, public_key: String, amount: u64) { - // call slash (the method, not the guitarist) - // remove the mixnode from the database - } - */ -} - -#[cfg(test)] -mod mixnodes { - use super::*; - - #[test] - fn adding_and_retrieving_works() { - let mock_db = MixminingDb::new(); - let mut service = Service::new(mock_db); - let node1 = tests::fake_mixnode("London, UK"); - - service.add(node1.clone()); - let nodes = service.topology().mixnodes; - assert_eq!(1, nodes.len()); - assert_eq!(node1.clone(), nodes[0]); - let node2 = tests::fake_mixnode("Neuchatel"); - - service.add(node2.clone()); - let nodes = service.topology().mixnodes; - assert_eq!(2, nodes.len()); - assert_eq!(node1, nodes[0]); - assert_eq!(node2, nodes[1]); - } -} - -#[cfg(test)] -mod constructor { - use super::*; - - #[test] - fn sets_database() { - let db = db::MixminingDb::new(); - let service = Service::new(db.clone()); - - assert_eq!(db, service.db); - } -} - -#[cfg(test)] -mod capacity { - use super::*; - - #[test] - fn setting_capacity_sends_correct_value_to_datastore() { - let mock_db = db::MixminingDb::new(); - let mut service = Service::new(mock_db); - - service.set_capacity(3); - - assert_eq!(3, service.capacity()); - } - - #[test] - fn getting_capacity_works() { - let mut mock_db = db::MixminingDb::new(); - mock_db.set_capacity(3); - let service = Service::new(mock_db); - assert_eq!(3, service.capacity()); - } -} diff --git a/validator/src/services/mixmining/models.rs b/validator/src/services/mixmining/models.rs deleted file mode 100644 index bff8702ed7..0000000000 --- a/validator/src/services/mixmining/models.rs +++ /dev/null @@ -1,51 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Mixnode { - pub host: String, - pub public_key: String, - pub last_seen: u64, - pub location: String, - pub stake: u64, - pub version: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ServiceProvider { - host: String, - public_key: String, - version: String, - last_seen: u64, - location: String, -} - -/// Topology shows us the current state of the overall Nym network -#[derive(Serialize, Deserialize, Debug)] -pub struct Topology { - pub mixnodes: Vec, - pub service_providers: Vec, - pub validators: Vec, -} - -impl Topology { - pub fn new( - mixnodes: Vec, - service_providers: Vec, - validators: Vec, - ) -> Topology { - Topology { - mixnodes, - service_providers, - validators, - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Validator { - host: String, - public_key: String, - version: String, - last_seen: u64, - location: String, -} diff --git a/validator/src/services/mixmining/tests/mod.rs b/validator/src/services/mixmining/tests/mod.rs deleted file mode 100644 index fac930f902..0000000000 --- a/validator/src/services/mixmining/tests/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(test)] -pub fn fake_mixnode(location: &str) -> super::Mixnode { - super::Mixnode { - host: String::from("foo.com"), - last_seen: 123, - location: String::from(location), - public_key: String::from("abc123"), - stake: 8, - version: String::from("1.0"), - } -} diff --git a/validator/src/validator.rs b/validator/src/validator.rs deleted file mode 100644 index 195a6ae9db..0000000000 --- a/validator/src/validator.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// use crate::config::Config; -use crate::network::rest; -use crate::network::tendermint; -use crate::services::mixmining; -use tokio::runtime::Runtime; - -pub struct Validator { - // when you re-introduce keys, check which ones you want: - // MixIdentityKeyPair (like 'nym-client' ) <- probably that one (after maybe renaming to just identity::KeyPair) - // encryption::KeyPair (like 'nym-mixnode' or 'sfw-provider') - tendermint_abci: tendermint::Abci, - rest_api: rest::Api, -} - -impl Validator { - pub fn new() -> Self { - let mixmining_db = mixmining::db::MixminingDb::new(); - let mixmining_service = mixmining::Service::new(mixmining_db); - - let rest_api = rest::Api::new(mixmining_service); - - Validator { - rest_api, - - // perhaps you might want to pass &config to the constructor - // there to get the config.tendermint (assuming you create appropriate fields + getters) - tendermint_abci: tendermint::Abci::new(), - } - } - - // TODO: Fix Tendermint startup here, see https://github.com/nymtech/nym/issues/147 - pub fn start(self) { - let mut rt = Runtime::new().unwrap(); - rt.spawn(self.rest_api.run()); - rt.spawn(self.tendermint_abci.run()); - - // TODO: this message is going to come out of order (if at all), as spawns are async, see issue above - println!("Validator startup complete."); - rt.block_on(blocker()); - } -} - -pub async fn blocker() {} // once Tendermint unblocks us, make this block forever.