diff --git a/api/api.js b/api/api.js
index 6427e123..aa591375 100644
--- a/api/api.js
+++ b/api/api.js
@@ -12,6 +12,7 @@ import WebSocket from 'ws';
import _ from 'lodash';
import './inbound-stream';
import expressWs from 'express-ws';
+import geoip from 'geoip-lite';
import config from './config';
@@ -252,6 +253,16 @@ app.get('/blk/:id', (req, res) => {
sendBlockResult(req, res);
});
+app.get('/geoip/:ip', (req, res) => {
+ const {ip} = req.params;
+ const geo = geoip.lookup(ip);
+ if (geo === null) {
+ res.status(404).send('{"error":"not_found"}\n');
+ } else {
+ res.send(JSON.stringify(geo.ll) + '\n');
+ }
+});
+
async function sendEntryResult(req, res) {
try {
let result = await hgetallAsync(`!ent:${req.params.id}`);
diff --git a/package.json b/package.json
index 379f632d..92d1d77a 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,8 @@
"cors": "^2.8.5",
"express": "^4.16.4",
"express-ws": "^4.0.0",
+ "geoip-lite": "^1.3.7",
+ "google-map-react": "^1.1.4",
"ip": "^1.1.5",
"lodash": "^4.17.11",
"nocache": "^2.0.0",
diff --git a/src/App.js b/src/App.js
index fcdc8536..b4651260 100755
--- a/src/App.js
+++ b/src/App.js
@@ -21,6 +21,7 @@ import BxTransactionChart from './BxTransactionChart';
import BxStatsTable from './BxStatsTable';
import BxDialog from './BxDialog';
import BxDialogTransactions from './BxDialogTransactions';
+import BxDialogWorldMap from './BxDialogWorldMap';
import BxAppBar from './BxAppBar';
const history = createBrowserHistory();
@@ -103,11 +104,35 @@ const styles = theme => ({
},
});
+async function geoip(ip: string) {
+ let lat = 11.6065;
+ let lng = 165.3768;
+
+ try {
+ const result = await window.fetch(
+ `http:${BLOCK_EXPLORER_API_BASE}/geoip/${ip}`,
+ );
+ if (result.status === 200) {
+ const json = await result.json();
+ lat = json[0];
+ lng = json[1];
+ console.log(ip, 'at', lat, lng);
+ }
+ } catch (err) {
+ console.log('geoip of', ip, 'failed with:', err);
+ }
+
+ // Add some Math.random() to prevent nodes in the same data center from fully
+ // overlaying each other
+ return [lat + Math.random() / 10 - 0.05, lng + Math.random() / 10 - 0.05];
+}
+
const BLOCK_EXPLORER_API_BASE = EndpointConfig.BLOCK_EXPLORER_API_BASE;
const BxAppBarThemed = withStyles(styles)(BxAppBar);
const BxDialogThemed = withStyles(styles)(BxDialog);
const BxDialogTransactionsThemed = withStyles(styles)(BxDialogTransactions);
+const BxDialogWorldMapThemed = withStyles(styles)(BxDialogWorldMap);
const BxStatsTableThemed = withStyles(styles)(BxStatsTable);
const BxTransactionChartThemed = withStyles(styles)(BxTransactionChart);
const BxDataTableThemed = withStyles(styles)(BxDataTable);
@@ -138,8 +163,9 @@ class App extends Component {
selectedValue: null,
currentMatch: null,
stateLoading: false,
+ nodes: [],
globalStats: {
- 'node-count': 0,
+ '!ent-last-leader': null,
'!blk-last-slot': 0,
'!txn-count': 0,
'!txn-per-sec-max': 0,
@@ -215,11 +241,31 @@ class App extends Component {
);
try {
- const nodes = await this.connection.getClusterNodes();
- this.updateSpecificGlobalStateAttribute('node-count', nodes.length);
+ const oldNodes = this.state.nodes;
+ const newNodes = await this.connection.getClusterNodes();
+
+ let modified = oldNodes.length !== newNodes.length;
+ for (const newNode of newNodes) {
+ const oldNode = oldNodes.find(node => node.id === newNode.id);
+ if (oldNode) {
+ newNode.lat = oldNode.lat;
+ newNode.lng = oldNode.lng;
+ } else {
+ const ip = newNode.gossip.split(':')[0];
+ const [lat, lng] = await geoip(ip);
+ newNode.lat = lat;
+ newNode.lng = lng;
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ console.log('newNodes', newNodes);
+ this.setState({nodes: newNodes});
+ }
} catch (err) {
- this.updateSpecificGlobalStateAttribute('node-count', '?');
- console.log(err);
+ this.setState({nodes: []});
+ console.log('getClusterNodes failed:', err.message);
}
}
@@ -471,6 +517,10 @@ class App extends Component {
history.push('/');
};
+ showMap = () => () => {
+ history.push(`/map`);
+ };
+
toggleEnabled = self => event => {
if (event.target.checked === self.state.enabled) {
return;
@@ -581,8 +631,20 @@ class App extends Component {
handleSearch={self.handleSearch(self)}
enabled={this.state.enabled}
handleSwitch={this.toggleEnabled(self)}
+ handleMap={this.showMap(self)}
/>
+ (
+
+ )}
+ />
-
+
diff --git a/src/BxAppBar.jsx b/src/BxAppBar.jsx
index c7a218a5..15f60f34 100644
--- a/src/BxAppBar.jsx
+++ b/src/BxAppBar.jsx
@@ -10,6 +10,7 @@ import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
//import MenuIcon from '@material-ui/icons/Menu';
import SearchIcon from '@material-ui/icons/Search';
+import MapIcon from '@material-ui/icons/Map';
// import AccountCircle from '@material-ui/icons/AccountCircle';
import MoreIcon from '@material-ui/icons/MoreVert';
import Switch from '@material-ui/core/Switch';
@@ -20,6 +21,12 @@ class BxAppBar extends React.Component {
mobileMoreAnchorEl: null,
};
+ handleMap = event => {
+ if (this.props.handleMap) {
+ this.props.handleMap(event);
+ }
+ };
+
handleSearch = event => {
if (this.props.handleSearch) {
this.props.handleSearch(event);
@@ -144,16 +151,9 @@ class BxAppBar extends React.Component {
- {/*
*/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
- {/*
*/}
- {/**/}
- {/**/}
- {/**/}
- {/**/}
+
+
+
{
+ this.setState({
+ anchorEl: event.currentTarget,
+ });
+ };
+
+ handleClose = () => {
+ this.setState({
+ anchorEl: null,
+ });
+ };
+
+ handleTerminate = async () => {
+ const {node} = this.props;
+ try {
+ const rpcUrl = `http://${node.rpc}`;
+ console.log(rpcUrl);
+ if (window.confirm('Are you sure you want to terminate this node?')) {
+ const connection = new Connection(rpcUrl);
+ const result = await connection.fullnodeExit();
+ if (!result) {
+ window.alert('Node declined to exit');
+ }
+ }
+ } catch (err) {
+ window.alert(`Failed to terminate node: ${err}`);
+ }
+ this.handleClose();
+ };
+
+ render() {
+ const {classes, isLeader, node, ...other} = this.props;
+ const {anchorEl} = this.state;
+ const open = Boolean(anchorEl);
+
+ return (
+
+
+
+
+
+
+ Node: {node.id}
+
+ Gossip: {node.gossip}
+
+ {node.rpc && (
+
+ )}
+
+
+ );
+ }
+}
+
+Node.propTypes = {
+ classes: PropTypes.object.isRequired,
+};
+
+export default class BxDialogWorldMap extends React.Component {
+ static defaultProps = {
+ center: {
+ lat: 38.747,
+ lng: -120.6743,
+ },
+ zoom: 1,
+ };
+
+ render() {
+ const {classes, onClose, leaderId, nodes, ...other} = this.props;
+
+ return (
+
+ );
+ }
+}
+
+BxDialogWorldMap.propTypes = {
+ classes: PropTypes.object.isRequired,
+ onClose: PropTypes.func,
+};
diff --git a/src/BxStatsTable.jsx b/src/BxStatsTable.jsx
index 6255542d..d0dd63bb 100644
--- a/src/BxStatsTable.jsx
+++ b/src/BxStatsTable.jsx
@@ -19,7 +19,7 @@ class BxStatsTable extends React.Component {
}
render() {
- const {globalStats} = this.props;
+ const {globalStats, nodeCount} = this.props;
let currentTpsKey = null;
_.forEach(globalStats, (v, k) => {
@@ -51,7 +51,7 @@ class BxStatsTable extends React.Component {
- {globalStats['node-count']}
+ {nodeCount}
diff --git a/yarn.lock b/yarn.lock
index 4e25cacc..4a5b4fee 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1247,6 +1247,11 @@
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/yargs" "^12.0.9"
+"@mapbox/point-geometry@^0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2"
+ integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=
+
"@material-ui/core@^3.9.3":
version "3.9.3"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-3.9.3.tgz#d378c1f4beb18df9a534ca7258c2c33fb8e0e51f"
@@ -2307,7 +2312,7 @@ async@^1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-async@^2.5.0, async@^2.6.1:
+async@^2.1.1, async@^2.5.0, async@^2.6.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
@@ -3353,6 +3358,11 @@ btoa-lite@^1.0.0:
resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc=
+buffer-crc32@~0.2.3:
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+ integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
+
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@@ -5493,6 +5503,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+eventemitter3@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
+ integrity sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=
+
eventemitter3@^3.0.0, eventemitter3@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
@@ -5802,6 +5817,13 @@ fbjs@^0.8.0, fbjs@^0.8.1:
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"
+fd-slicer@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
+ integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+ dependencies:
+ pend "~1.2.0"
+
figgy-pudding@^3.0.0, figgy-pudding@^3.1.0, figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@@ -6181,6 +6203,19 @@ gentle-fs@^2.0.0, gentle-fs@^2.0.1:
read-cmd-shim "^1.0.1"
slide "^1.1.6"
+geoip-lite@^1.3.7:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/geoip-lite/-/geoip-lite-1.3.7.tgz#6fce24bf4a23b06bd2dce6b2d20c8e7f49b8437a"
+ integrity sha512-hdlRVXTk/cFFXbeDl0KdcDSH2Gzlyp7050Q+ml+g9Y0BkygjDklwF/nKcouzHWWzPsBRIp0EJXiKaTqJ0hlDSQ==
+ dependencies:
+ async "^2.1.1"
+ colors "^1.1.2"
+ iconv-lite "^0.4.13"
+ ip-address "^5.8.9"
+ lazy "^1.0.11"
+ rimraf "^2.5.2"
+ yauzl "^2.9.2"
+
get-caller-file@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
@@ -6342,6 +6377,15 @@ globby@^9.0.0:
pify "^4.0.1"
slash "^2.0.0"
+google-map-react@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/google-map-react/-/google-map-react-1.1.4.tgz#ebf649b3cb985c645a0b7a05eb4e1c3f80482c80"
+ integrity sha512-+XcOu7QrRrlDa7Bk25oVV4FSsvMorAupuBrnDjA28Zz+HE+wEz79igqn+0KCHbemjgEHz51sHk0wm+IodvLoMg==
+ dependencies:
+ "@mapbox/point-geometry" "^0.1.0"
+ eventemitter3 "^1.1.0"
+ scriptjs "^2.5.7"
+
got@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@@ -6766,7 +6810,7 @@ iconv-lite@0.4.23:
dependencies:
safer-buffer ">= 2.1.2 < 3"
-iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
+iconv-lite@0.4.24, iconv-lite@^0.4.13, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -6988,6 +7032,19 @@ invert-kv@^2.0.0:
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
+ip-address@^5.8.9:
+ version "5.9.0"
+ resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-5.9.0.tgz#e501b3a0da9e31d9a14ef7ccdc05c5552c465954"
+ integrity sha512-+4yKpEyent8IpjuDQVkIpzIDbxSlCHTPdmaXCRLH0ttt3YsrbNxuZJ6h+1wLPx10T7gWsLN7M6BXIHV2vZNOGw==
+ dependencies:
+ jsbn "1.1.0"
+ lodash.find "^4.6.0"
+ lodash.max "^4.0.1"
+ lodash.merge "^4.6.1"
+ lodash.padstart "^4.6.1"
+ lodash.repeat "^4.1.0"
+ sprintf-js "1.1.1"
+
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -7949,6 +8006,11 @@ js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.9.0:
argparse "^1.0.7"
esprima "^4.0.0"
+jsbn@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
+ integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA=
+
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@@ -8260,6 +8322,11 @@ lazy-property@~1.0.0:
resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147"
integrity sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=
+lazy@^1.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690"
+ integrity sha1-2qBoIGKCVCwIgojpdcKXwa53tpA=
+
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
@@ -8470,6 +8537,11 @@ lodash.filter@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=
+lodash.find@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
+ integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=
+
lodash.flatten@^4.2.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
@@ -8500,16 +8572,26 @@ lodash.map@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=
+lodash.max@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.max/-/lodash.max-4.0.1.tgz#8735566c618b35a9f760520b487ae79658af136a"
+ integrity sha1-hzVWbGGLNan3YFILSHrnllivE2o=
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
-lodash.merge@^4.4.0:
+lodash.merge@^4.4.0, lodash.merge@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==
+lodash.padstart@^4.6.1:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b"
+ integrity sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=
+
lodash.pick@^4.2.1:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
@@ -8525,6 +8607,11 @@ lodash.reject@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415"
integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=
+lodash.repeat@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/lodash.repeat/-/lodash.repeat-4.1.0.tgz#fc7de8131d8c8ac07e4b49f74ffe829d1f2bec44"
+ integrity sha1-/H3oEx2MisB+S0n3T/6CnR8r7EQ=
+
lodash.set@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
@@ -10206,6 +10293,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+ integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
+
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -12200,6 +12292,11 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
+scriptjs@^2.5.7:
+ version "2.5.9"
+ resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f"
+ integrity sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -12739,6 +12836,11 @@ split@^1.0.0:
dependencies:
through "2"
+sprintf-js@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c"
+ integrity sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=
+
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -14351,3 +14453,11 @@ yargs@^12.0.0, yargs@^12.0.2, yargs@^12.0.5:
which-module "^2.0.0"
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
+
+yauzl@^2.9.2:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
+ integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
+ dependencies:
+ buffer-crc32 "~0.2.3"
+ fd-slicer "~1.1.0"