├── .gitignore ├── LICENSE ├── README.md ├── config.sample.js ├── package-lock.json ├── package.json ├── public ├── app.js ├── index.html ├── style.css └── vendor │ ├── fonts │ ├── ionicons.eot │ ├── ionicons.svg │ ├── ionicons.ttf │ └── ionicons.woff │ ├── ionicons.min.css │ └── prism-base16-brewer.light.css ├── screenshots ├── monitor-feed.gif ├── monitor-graph.gif └── setting-up-webhooks.png └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.js 3 | npm-debug.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Stripe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stripe Webhook Monitor 2 | 3 | 🛑 Deprecated 🛑 - This repository is no longer being maintained. 4 | 5 | `stripe-webhook-monitor` is a real-time monitor for Stripe webhooks that provides a live feeds and graph of recent events. 6 | 7 | 8 | 9 | Stripe's platform includes [webhooks](https://stripe.com/docs/webhooks) that will notify you when actions take place on your account. For example, you can be notified when: 10 | 11 | - New charges are created (`charge.created`) 12 | - Customers subscribe to a new plan (`customer.subscription.created`) 13 | - Payouts and transfers are completed (`payout.paid`) 14 | - An invoice payment failed (`invoice.payment_failed`) 15 | 16 | Webhooks are powerful: you can subscribe to these notifications and programmatically react to them in real-time. 17 | 18 | ## Getting started 19 | 20 | ### Requirements 21 | You'll need to have Node v7.6+ installed, which includes support for `async` / `await`. 22 | 23 | ### Set up the monitor 24 | Clone the project repository, and create a configuration for your Stripe account: 25 | 26 | ``` 27 | cp config.sample.js config.js 28 | ``` 29 | 30 | You'll need to fill in your Stripe secret key. 31 | 32 | Webhooks require a public URL that Stripe will ping to notify the monitor of new events. Support for [ngrok](https://ngrok.com/) is included out of the box: ngrok will create a secure tunnel and provide a public URL to your local machine. 33 | 34 | If you have a [__Basic__](https://ngrok.com/pricing) ngrok subscription, you can specify a custom subdomain that will stay reserved for your account. 35 | 36 | ### Start receiving changes 37 | 38 | To start the monitor: 39 | 40 | ``` 41 | npm install 42 | npm start 43 | ``` 44 | 45 | Take note of the public URL provided by ngrok: it should be listed when the monitor starts. 46 | 47 | **Don't want to use ngrok?** As long as Stripe can reach the webhooks endpoint via a public URL, you'll receive updates. 48 | 49 | ### Subscribe to webhook notifications 50 | 51 | In your Stripe Dashboard, go to the _API_ section, then click on the _Webhooks_ tab. 52 | 53 | You should add a receiving endpoint by clicking _Add Endpoint_. Fill in the public URL provided by ngrok, or any other public URL that can reach the webhook monitor. 54 | 55 | ![](https://raw.githubusercontent.com/stripe/stripe-webhook-monitor/master/screenshots/setting-up-webhooks.png) 56 | 57 | ## Troubleshooting 58 | 59 | ### I'm not receiving real-time updates 60 | 61 | - Check that the [Stripe Dashboard](https://dashboard.stripe.com/webhooks/) is listing your webhook route as _Enabled_. 62 | - Make sure that the webhook endpoint matches the URL printed in your console. 63 | 64 | ## Credits 65 | 66 | - Code: [Michael Glukhovsky](https://twitter.com/mglukhovsky) 67 | - Icons: [Ionicons](http://ionicons.com/) 68 | -------------------------------------------------------------------------------- /config.sample.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | port: 4000, 5 | stripe: { 6 | // Include your Stripe secret key here 7 | secretKey: 'YOUR_STRIPE_SECRET_KEY' 8 | }, 9 | /* 10 | Stripe needs a public URL for our server that it can ping with new events. 11 | If ngrok is enabled, this server will create a public endpoint for you. 12 | */ 13 | ngrok: { 14 | enabled: true, 15 | /* 16 | Optional: if you have a Pro ngrok account you can provide a custom 17 | subdomain and your authentication token here. 18 | */ 19 | subdomain: null, 20 | authtoken: null 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stripe-webhook-monitor", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "8.10.49", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.49.tgz", 10 | "integrity": "sha512-YX30JVx0PvSmJ3Eqr74fYLGeBxD+C7vIL20ek+GGGLJeUbVYRUW3EzyAXpIRA0K8c8o0UWqR/GwEFYiFoz1T8w==" 11 | }, 12 | "abbrev": { 13 | "version": "1.1.1", 14 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 15 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 16 | }, 17 | "accepts": { 18 | "version": "1.3.7", 19 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 20 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 21 | "requires": { 22 | "mime-types": "~2.1.24", 23 | "negotiator": "0.6.2" 24 | } 25 | }, 26 | "after": { 27 | "version": "0.8.2", 28 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 29 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" 30 | }, 31 | "ajv": { 32 | "version": "6.10.0", 33 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", 34 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", 35 | "requires": { 36 | "fast-deep-equal": "^2.0.1", 37 | "fast-json-stable-stringify": "^2.0.0", 38 | "json-schema-traverse": "^0.4.1", 39 | "uri-js": "^4.2.2" 40 | } 41 | }, 42 | "ansi-align": { 43 | "version": "2.0.0", 44 | "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", 45 | "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", 46 | "requires": { 47 | "string-width": "^2.0.0" 48 | } 49 | }, 50 | "ansi-regex": { 51 | "version": "3.0.0", 52 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 53 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 54 | }, 55 | "ansi-styles": { 56 | "version": "3.2.0", 57 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", 58 | "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", 59 | "requires": { 60 | "color-convert": "^1.9.0" 61 | } 62 | }, 63 | "app-root-path": { 64 | "version": "2.0.1", 65 | "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", 66 | "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", 67 | "dev": true 68 | }, 69 | "argparse": { 70 | "version": "1.0.9", 71 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 72 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", 73 | "dev": true, 74 | "requires": { 75 | "sprintf-js": "~1.0.2" 76 | } 77 | }, 78 | "arraybuffer.slice": { 79 | "version": "0.0.7", 80 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 81 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" 82 | }, 83 | "asn1": { 84 | "version": "0.2.4", 85 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 86 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 87 | "requires": { 88 | "safer-buffer": "~2.1.0" 89 | } 90 | }, 91 | "assert-plus": { 92 | "version": "1.0.0", 93 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 94 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 95 | }, 96 | "async-limiter": { 97 | "version": "1.0.0", 98 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 99 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 100 | }, 101 | "asynckit": { 102 | "version": "0.4.0", 103 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 104 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 105 | }, 106 | "aws-sign2": { 107 | "version": "0.7.0", 108 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 109 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 110 | }, 111 | "aws4": { 112 | "version": "1.8.0", 113 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 114 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 115 | }, 116 | "backo2": { 117 | "version": "1.0.2", 118 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 119 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" 120 | }, 121 | "balanced-match": { 122 | "version": "1.0.0", 123 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 124 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 125 | "dev": true 126 | }, 127 | "base64-arraybuffer": { 128 | "version": "0.1.5", 129 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 130 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 131 | }, 132 | "base64id": { 133 | "version": "1.0.0", 134 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", 135 | "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" 136 | }, 137 | "bcrypt-pbkdf": { 138 | "version": "1.0.2", 139 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 140 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 141 | "requires": { 142 | "tweetnacl": "^0.14.3" 143 | } 144 | }, 145 | "better-assert": { 146 | "version": "1.0.2", 147 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 148 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 149 | "requires": { 150 | "callsite": "1.0.0" 151 | } 152 | }, 153 | "binary": { 154 | "version": "0.3.0", 155 | "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", 156 | "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", 157 | "requires": { 158 | "buffers": "~0.1.1", 159 | "chainsaw": "~0.1.0" 160 | } 161 | }, 162 | "blob": { 163 | "version": "0.0.5", 164 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 165 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" 166 | }, 167 | "body-parser": { 168 | "version": "1.18.2", 169 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 170 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 171 | "requires": { 172 | "bytes": "3.0.0", 173 | "content-type": "~1.0.4", 174 | "debug": "2.6.9", 175 | "depd": "~1.1.1", 176 | "http-errors": "~1.6.2", 177 | "iconv-lite": "0.4.19", 178 | "on-finished": "~2.3.0", 179 | "qs": "6.5.1", 180 | "raw-body": "2.3.2", 181 | "type-is": "~1.6.15" 182 | }, 183 | "dependencies": { 184 | "bytes": { 185 | "version": "3.0.0", 186 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 187 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 188 | }, 189 | "content-type": { 190 | "version": "1.0.4", 191 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 192 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 193 | }, 194 | "debug": { 195 | "version": "2.6.9", 196 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 197 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 198 | "requires": { 199 | "ms": "2.0.0" 200 | } 201 | }, 202 | "depd": { 203 | "version": "1.1.1", 204 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 205 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 206 | }, 207 | "ee-first": { 208 | "version": "1.1.1", 209 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 210 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 211 | }, 212 | "http-errors": { 213 | "version": "1.6.2", 214 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 215 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 216 | "requires": { 217 | "depd": "1.1.1", 218 | "inherits": "2.0.3", 219 | "setprototypeof": "1.0.3", 220 | "statuses": ">= 1.3.1 < 2" 221 | } 222 | }, 223 | "iconv-lite": { 224 | "version": "0.4.19", 225 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 226 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 227 | }, 228 | "inherits": { 229 | "version": "2.0.3", 230 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 231 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 232 | }, 233 | "media-typer": { 234 | "version": "0.3.0", 235 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 236 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 237 | }, 238 | "mime-db": { 239 | "version": "1.30.0", 240 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 241 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 242 | }, 243 | "mime-types": { 244 | "version": "2.1.17", 245 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 246 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 247 | "requires": { 248 | "mime-db": "~1.30.0" 249 | } 250 | }, 251 | "ms": { 252 | "version": "2.0.0", 253 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 254 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 255 | }, 256 | "on-finished": { 257 | "version": "2.3.0", 258 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 259 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 260 | "requires": { 261 | "ee-first": "1.1.1" 262 | } 263 | }, 264 | "qs": { 265 | "version": "6.5.1", 266 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 267 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 268 | }, 269 | "raw-body": { 270 | "version": "2.3.2", 271 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 272 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 273 | "requires": { 274 | "bytes": "3.0.0", 275 | "http-errors": "1.6.2", 276 | "iconv-lite": "0.4.19", 277 | "unpipe": "1.0.0" 278 | } 279 | }, 280 | "setprototypeof": { 281 | "version": "1.0.3", 282 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 283 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 284 | }, 285 | "statuses": { 286 | "version": "1.3.1", 287 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 288 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 289 | }, 290 | "type-is": { 291 | "version": "1.6.15", 292 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 293 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 294 | "requires": { 295 | "media-typer": "0.3.0", 296 | "mime-types": "~2.1.15" 297 | } 298 | }, 299 | "unpipe": { 300 | "version": "1.0.0", 301 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 302 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 303 | } 304 | } 305 | }, 306 | "boxen": { 307 | "version": "1.2.1", 308 | "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.2.1.tgz", 309 | "integrity": "sha1-DxHn/jRO25OXl3/BPt5/ZNlWSB0=", 310 | "requires": { 311 | "ansi-align": "^2.0.0", 312 | "camelcase": "^4.0.0", 313 | "chalk": "^2.0.1", 314 | "cli-boxes": "^1.0.0", 315 | "string-width": "^2.0.0", 316 | "term-size": "^1.2.0", 317 | "widest-line": "^1.0.0" 318 | } 319 | }, 320 | "brace-expansion": { 321 | "version": "1.1.8", 322 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 323 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 324 | "dev": true, 325 | "requires": { 326 | "balanced-match": "^1.0.0", 327 | "concat-map": "0.0.1" 328 | } 329 | }, 330 | "buffers": { 331 | "version": "0.1.1", 332 | "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", 333 | "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" 334 | }, 335 | "callsite": { 336 | "version": "1.0.0", 337 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 338 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" 339 | }, 340 | "camelcase": { 341 | "version": "4.1.0", 342 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", 343 | "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" 344 | }, 345 | "caseless": { 346 | "version": "0.12.0", 347 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 348 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 349 | }, 350 | "chainsaw": { 351 | "version": "0.1.0", 352 | "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", 353 | "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", 354 | "requires": { 355 | "traverse": ">=0.3.0 <0.4" 356 | } 357 | }, 358 | "chalk": { 359 | "version": "2.1.0", 360 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", 361 | "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", 362 | "requires": { 363 | "ansi-styles": "^3.1.0", 364 | "escape-string-regexp": "^1.0.5", 365 | "supports-color": "^4.0.0" 366 | } 367 | }, 368 | "ci-info": { 369 | "version": "1.1.1", 370 | "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.1.tgz", 371 | "integrity": "sha512-vHDDF/bP9RYpTWtUhpJRhCFdvvp3iDWvEbuDbWgvjUrNGV1MXJrE0MPcwGtEled04m61iwdBLUIHZtDgzWS4ZQ==", 372 | "dev": true 373 | }, 374 | "cli-boxes": { 375 | "version": "1.0.0", 376 | "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", 377 | "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" 378 | }, 379 | "cli-cursor": { 380 | "version": "1.0.2", 381 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", 382 | "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", 383 | "dev": true, 384 | "requires": { 385 | "restore-cursor": "^1.0.1" 386 | } 387 | }, 388 | "cli-spinners": { 389 | "version": "0.1.2", 390 | "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", 391 | "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", 392 | "dev": true 393 | }, 394 | "cli-truncate": { 395 | "version": "0.2.1", 396 | "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", 397 | "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", 398 | "dev": true, 399 | "requires": { 400 | "slice-ansi": "0.0.4", 401 | "string-width": "^1.0.1" 402 | }, 403 | "dependencies": { 404 | "ansi-regex": { 405 | "version": "2.1.1", 406 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 407 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 408 | "dev": true 409 | }, 410 | "is-fullwidth-code-point": { 411 | "version": "1.0.0", 412 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 413 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 414 | "dev": true, 415 | "requires": { 416 | "number-is-nan": "^1.0.0" 417 | } 418 | }, 419 | "string-width": { 420 | "version": "1.0.2", 421 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 422 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 423 | "dev": true, 424 | "requires": { 425 | "code-point-at": "^1.0.0", 426 | "is-fullwidth-code-point": "^1.0.0", 427 | "strip-ansi": "^3.0.0" 428 | } 429 | }, 430 | "strip-ansi": { 431 | "version": "3.0.1", 432 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 433 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 434 | "dev": true, 435 | "requires": { 436 | "ansi-regex": "^2.0.0" 437 | } 438 | } 439 | } 440 | }, 441 | "code-point-at": { 442 | "version": "1.1.0", 443 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 444 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 445 | }, 446 | "color-convert": { 447 | "version": "1.9.0", 448 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", 449 | "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", 450 | "requires": { 451 | "color-name": "^1.1.1" 452 | } 453 | }, 454 | "color-name": { 455 | "version": "1.1.3", 456 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 457 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 458 | }, 459 | "combined-stream": { 460 | "version": "1.0.8", 461 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 462 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 463 | "requires": { 464 | "delayed-stream": "~1.0.0" 465 | } 466 | }, 467 | "commander": { 468 | "version": "2.11.0", 469 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", 470 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", 471 | "dev": true 472 | }, 473 | "component-bind": { 474 | "version": "1.0.0", 475 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 476 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" 477 | }, 478 | "component-emitter": { 479 | "version": "1.2.1", 480 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 481 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 482 | }, 483 | "component-inherit": { 484 | "version": "0.0.3", 485 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 486 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" 487 | }, 488 | "concat-map": { 489 | "version": "0.0.1", 490 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 491 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 492 | "dev": true 493 | }, 494 | "cookie": { 495 | "version": "0.3.1", 496 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 497 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 498 | }, 499 | "core-util-is": { 500 | "version": "1.0.2", 501 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 502 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 503 | }, 504 | "cosmiconfig": { 505 | "version": "1.1.0", 506 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz", 507 | "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=", 508 | "dev": true, 509 | "requires": { 510 | "graceful-fs": "^4.1.2", 511 | "js-yaml": "^3.4.3", 512 | "minimist": "^1.2.0", 513 | "object-assign": "^4.0.1", 514 | "os-homedir": "^1.0.1", 515 | "parse-json": "^2.2.0", 516 | "pinkie-promise": "^2.0.0", 517 | "require-from-string": "^1.1.0" 518 | }, 519 | "dependencies": { 520 | "minimist": { 521 | "version": "1.2.0", 522 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 523 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 524 | "dev": true 525 | } 526 | } 527 | }, 528 | "cross-spawn": { 529 | "version": "5.1.0", 530 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", 531 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", 532 | "requires": { 533 | "lru-cache": "^4.0.1", 534 | "shebang-command": "^1.2.0", 535 | "which": "^1.2.9" 536 | } 537 | }, 538 | "dashdash": { 539 | "version": "1.14.1", 540 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 541 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 542 | "requires": { 543 | "assert-plus": "^1.0.0" 544 | } 545 | }, 546 | "date-fns": { 547 | "version": "1.29.0", 548 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", 549 | "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==", 550 | "dev": true 551 | }, 552 | "debug": { 553 | "version": "4.1.1", 554 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 555 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 556 | "requires": { 557 | "ms": "^2.1.1" 558 | } 559 | }, 560 | "decompress-zip": { 561 | "version": "0.3.2", 562 | "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.2.tgz", 563 | "integrity": "sha512-Ab1QY4LrWMrUuo53lLnmGOby7v8ryqxJ+bKibKSiPisx+25mhut1dScVBXAYx14i/PqSrFZvR2FRRazhLbvL+g==", 564 | "requires": { 565 | "binary": "^0.3.0", 566 | "graceful-fs": "^4.1.3", 567 | "mkpath": "^0.1.0", 568 | "nopt": "^3.0.1", 569 | "q": "^1.1.2", 570 | "readable-stream": "^1.1.8", 571 | "touch": "0.0.3" 572 | } 573 | }, 574 | "delayed-stream": { 575 | "version": "1.0.0", 576 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 577 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 578 | }, 579 | "ecc-jsbn": { 580 | "version": "0.1.2", 581 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 582 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 583 | "requires": { 584 | "jsbn": "~0.1.0", 585 | "safer-buffer": "^2.1.0" 586 | } 587 | }, 588 | "elegant-spinner": { 589 | "version": "1.0.1", 590 | "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", 591 | "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", 592 | "dev": true 593 | }, 594 | "engine.io": { 595 | "version": "3.3.2", 596 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz", 597 | "integrity": "sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==", 598 | "requires": { 599 | "accepts": "~1.3.4", 600 | "base64id": "1.0.0", 601 | "cookie": "0.3.1", 602 | "debug": "~3.1.0", 603 | "engine.io-parser": "~2.1.0", 604 | "ws": "~6.1.0" 605 | }, 606 | "dependencies": { 607 | "debug": { 608 | "version": "3.1.0", 609 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 610 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 611 | "requires": { 612 | "ms": "2.0.0" 613 | } 614 | }, 615 | "ms": { 616 | "version": "2.0.0", 617 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 618 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 619 | } 620 | } 621 | }, 622 | "engine.io-client": { 623 | "version": "3.3.2", 624 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz", 625 | "integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==", 626 | "requires": { 627 | "component-emitter": "1.2.1", 628 | "component-inherit": "0.0.3", 629 | "debug": "~3.1.0", 630 | "engine.io-parser": "~2.1.1", 631 | "has-cors": "1.1.0", 632 | "indexof": "0.0.1", 633 | "parseqs": "0.0.5", 634 | "parseuri": "0.0.5", 635 | "ws": "~6.1.0", 636 | "xmlhttprequest-ssl": "~1.5.4", 637 | "yeast": "0.1.2" 638 | }, 639 | "dependencies": { 640 | "debug": { 641 | "version": "3.1.0", 642 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 643 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 644 | "requires": { 645 | "ms": "2.0.0" 646 | } 647 | }, 648 | "ms": { 649 | "version": "2.0.0", 650 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 651 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 652 | } 653 | } 654 | }, 655 | "engine.io-parser": { 656 | "version": "2.1.3", 657 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", 658 | "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", 659 | "requires": { 660 | "after": "0.8.2", 661 | "arraybuffer.slice": "~0.0.7", 662 | "base64-arraybuffer": "0.1.5", 663 | "blob": "0.0.5", 664 | "has-binary2": "~1.0.2" 665 | } 666 | }, 667 | "error-ex": { 668 | "version": "1.3.1", 669 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", 670 | "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", 671 | "dev": true, 672 | "requires": { 673 | "is-arrayish": "^0.2.1" 674 | } 675 | }, 676 | "escape-string-regexp": { 677 | "version": "1.0.5", 678 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 679 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 680 | }, 681 | "esprima": { 682 | "version": "4.0.0", 683 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", 684 | "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", 685 | "dev": true 686 | }, 687 | "execa": { 688 | "version": "0.7.0", 689 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", 690 | "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", 691 | "requires": { 692 | "cross-spawn": "^5.0.1", 693 | "get-stream": "^3.0.0", 694 | "is-stream": "^1.1.0", 695 | "npm-run-path": "^2.0.0", 696 | "p-finally": "^1.0.0", 697 | "signal-exit": "^3.0.0", 698 | "strip-eof": "^1.0.0" 699 | } 700 | }, 701 | "exit-hook": { 702 | "version": "1.1.1", 703 | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", 704 | "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", 705 | "dev": true 706 | }, 707 | "express": { 708 | "version": "4.16.2", 709 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", 710 | "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", 711 | "requires": { 712 | "accepts": "~1.3.4", 713 | "array-flatten": "1.1.1", 714 | "body-parser": "1.18.2", 715 | "content-disposition": "0.5.2", 716 | "content-type": "~1.0.4", 717 | "cookie": "0.3.1", 718 | "cookie-signature": "1.0.6", 719 | "debug": "2.6.9", 720 | "depd": "~1.1.1", 721 | "encodeurl": "~1.0.1", 722 | "escape-html": "~1.0.3", 723 | "etag": "~1.8.1", 724 | "finalhandler": "1.1.0", 725 | "fresh": "0.5.2", 726 | "merge-descriptors": "1.0.1", 727 | "methods": "~1.1.2", 728 | "on-finished": "~2.3.0", 729 | "parseurl": "~1.3.2", 730 | "path-to-regexp": "0.1.7", 731 | "proxy-addr": "~2.0.2", 732 | "qs": "6.5.1", 733 | "range-parser": "~1.2.0", 734 | "safe-buffer": "5.1.1", 735 | "send": "0.16.1", 736 | "serve-static": "1.13.1", 737 | "setprototypeof": "1.1.0", 738 | "statuses": "~1.3.1", 739 | "type-is": "~1.6.15", 740 | "utils-merge": "1.0.1", 741 | "vary": "~1.1.2" 742 | }, 743 | "dependencies": { 744 | "accepts": { 745 | "version": "1.3.4", 746 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", 747 | "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", 748 | "requires": { 749 | "mime-types": "~2.1.16", 750 | "negotiator": "0.6.1" 751 | } 752 | }, 753 | "array-flatten": { 754 | "version": "1.1.1", 755 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 756 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 757 | }, 758 | "content-disposition": { 759 | "version": "0.5.2", 760 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 761 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 762 | }, 763 | "content-type": { 764 | "version": "1.0.4", 765 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 766 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 767 | }, 768 | "cookie": { 769 | "version": "0.3.1", 770 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 771 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 772 | }, 773 | "cookie-signature": { 774 | "version": "1.0.6", 775 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 776 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 777 | }, 778 | "debug": { 779 | "version": "2.6.9", 780 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 781 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 782 | "requires": { 783 | "ms": "2.0.0" 784 | } 785 | }, 786 | "depd": { 787 | "version": "1.1.1", 788 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 789 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 790 | }, 791 | "destroy": { 792 | "version": "1.0.4", 793 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 794 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 795 | }, 796 | "ee-first": { 797 | "version": "1.1.1", 798 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 799 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 800 | }, 801 | "encodeurl": { 802 | "version": "1.0.1", 803 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 804 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 805 | }, 806 | "escape-html": { 807 | "version": "1.0.3", 808 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 809 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 810 | }, 811 | "etag": { 812 | "version": "1.8.1", 813 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 814 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 815 | }, 816 | "finalhandler": { 817 | "version": "1.1.0", 818 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", 819 | "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", 820 | "requires": { 821 | "debug": "2.6.9", 822 | "encodeurl": "~1.0.1", 823 | "escape-html": "~1.0.3", 824 | "on-finished": "~2.3.0", 825 | "parseurl": "~1.3.2", 826 | "statuses": "~1.3.1", 827 | "unpipe": "~1.0.0" 828 | } 829 | }, 830 | "forwarded": { 831 | "version": "0.1.2", 832 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 833 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 834 | }, 835 | "fresh": { 836 | "version": "0.5.2", 837 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 838 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 839 | }, 840 | "http-errors": { 841 | "version": "1.6.2", 842 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 843 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 844 | "requires": { 845 | "depd": "1.1.1", 846 | "inherits": "2.0.3", 847 | "setprototypeof": "1.0.3", 848 | "statuses": ">= 1.3.1 < 2" 849 | }, 850 | "dependencies": { 851 | "setprototypeof": { 852 | "version": "1.0.3", 853 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 854 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 855 | } 856 | } 857 | }, 858 | "inherits": { 859 | "version": "2.0.3", 860 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 861 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 862 | }, 863 | "ipaddr.js": { 864 | "version": "1.5.2", 865 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", 866 | "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" 867 | }, 868 | "media-typer": { 869 | "version": "0.3.0", 870 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 871 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 872 | }, 873 | "merge-descriptors": { 874 | "version": "1.0.1", 875 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 876 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 877 | }, 878 | "methods": { 879 | "version": "1.1.2", 880 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 881 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 882 | }, 883 | "mime": { 884 | "version": "1.4.1", 885 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 886 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 887 | }, 888 | "mime-db": { 889 | "version": "1.30.0", 890 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 891 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 892 | }, 893 | "mime-types": { 894 | "version": "2.1.17", 895 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 896 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 897 | "requires": { 898 | "mime-db": "~1.30.0" 899 | } 900 | }, 901 | "ms": { 902 | "version": "2.0.0", 903 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 904 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 905 | }, 906 | "negotiator": { 907 | "version": "0.6.1", 908 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 909 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 910 | }, 911 | "on-finished": { 912 | "version": "2.3.0", 913 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 914 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 915 | "requires": { 916 | "ee-first": "1.1.1" 917 | } 918 | }, 919 | "parseurl": { 920 | "version": "1.3.2", 921 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 922 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 923 | }, 924 | "path-to-regexp": { 925 | "version": "0.1.7", 926 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 927 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 928 | }, 929 | "proxy-addr": { 930 | "version": "2.0.2", 931 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", 932 | "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", 933 | "requires": { 934 | "forwarded": "~0.1.2", 935 | "ipaddr.js": "1.5.2" 936 | } 937 | }, 938 | "qs": { 939 | "version": "6.5.1", 940 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 941 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 942 | }, 943 | "range-parser": { 944 | "version": "1.2.0", 945 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 946 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 947 | }, 948 | "safe-buffer": { 949 | "version": "5.1.1", 950 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 951 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 952 | }, 953 | "send": { 954 | "version": "0.16.1", 955 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", 956 | "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", 957 | "requires": { 958 | "debug": "2.6.9", 959 | "depd": "~1.1.1", 960 | "destroy": "~1.0.4", 961 | "encodeurl": "~1.0.1", 962 | "escape-html": "~1.0.3", 963 | "etag": "~1.8.1", 964 | "fresh": "0.5.2", 965 | "http-errors": "~1.6.2", 966 | "mime": "1.4.1", 967 | "ms": "2.0.0", 968 | "on-finished": "~2.3.0", 969 | "range-parser": "~1.2.0", 970 | "statuses": "~1.3.1" 971 | } 972 | }, 973 | "serve-static": { 974 | "version": "1.13.1", 975 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", 976 | "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", 977 | "requires": { 978 | "encodeurl": "~1.0.1", 979 | "escape-html": "~1.0.3", 980 | "parseurl": "~1.3.2", 981 | "send": "0.16.1" 982 | } 983 | }, 984 | "setprototypeof": { 985 | "version": "1.1.0", 986 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 987 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 988 | }, 989 | "statuses": { 990 | "version": "1.3.1", 991 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 992 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 993 | }, 994 | "type-is": { 995 | "version": "1.6.15", 996 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 997 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 998 | "requires": { 999 | "media-typer": "0.3.0", 1000 | "mime-types": "~2.1.15" 1001 | } 1002 | }, 1003 | "unpipe": { 1004 | "version": "1.0.0", 1005 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1006 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1007 | }, 1008 | "utils-merge": { 1009 | "version": "1.0.1", 1010 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1011 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1012 | }, 1013 | "vary": { 1014 | "version": "1.1.2", 1015 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1016 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1017 | } 1018 | } 1019 | }, 1020 | "extend": { 1021 | "version": "3.0.2", 1022 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 1023 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 1024 | }, 1025 | "extsprintf": { 1026 | "version": "1.3.0", 1027 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 1028 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 1029 | }, 1030 | "fast-deep-equal": { 1031 | "version": "2.0.1", 1032 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 1033 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 1034 | }, 1035 | "fast-json-stable-stringify": { 1036 | "version": "2.0.0", 1037 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 1038 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 1039 | }, 1040 | "figures": { 1041 | "version": "1.7.0", 1042 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 1043 | "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", 1044 | "dev": true, 1045 | "requires": { 1046 | "escape-string-regexp": "^1.0.5", 1047 | "object-assign": "^4.1.0" 1048 | } 1049 | }, 1050 | "forever-agent": { 1051 | "version": "0.6.1", 1052 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 1053 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 1054 | }, 1055 | "form-data": { 1056 | "version": "2.3.3", 1057 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 1058 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 1059 | "requires": { 1060 | "asynckit": "^0.4.0", 1061 | "combined-stream": "^1.0.6", 1062 | "mime-types": "^2.1.12" 1063 | } 1064 | }, 1065 | "get-own-enumerable-property-symbols": { 1066 | "version": "2.0.1", 1067 | "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz", 1068 | "integrity": "sha512-TtY/sbOemiMKPRUDDanGCSgBYe7Mf0vbRsWnBZ+9yghpZ1MvcpSpuZFjHdEeY/LZjZy0vdLjS77L6HosisFiug==", 1069 | "dev": true 1070 | }, 1071 | "get-stream": { 1072 | "version": "3.0.0", 1073 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", 1074 | "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" 1075 | }, 1076 | "getpass": { 1077 | "version": "0.1.7", 1078 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 1079 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 1080 | "requires": { 1081 | "assert-plus": "^1.0.0" 1082 | } 1083 | }, 1084 | "graceful-fs": { 1085 | "version": "4.1.11", 1086 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 1087 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" 1088 | }, 1089 | "har-schema": { 1090 | "version": "2.0.0", 1091 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 1092 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 1093 | }, 1094 | "har-validator": { 1095 | "version": "5.1.3", 1096 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 1097 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 1098 | "requires": { 1099 | "ajv": "^6.5.5", 1100 | "har-schema": "^2.0.0" 1101 | } 1102 | }, 1103 | "has-ansi": { 1104 | "version": "2.0.0", 1105 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 1106 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 1107 | "dev": true, 1108 | "requires": { 1109 | "ansi-regex": "^2.0.0" 1110 | }, 1111 | "dependencies": { 1112 | "ansi-regex": { 1113 | "version": "2.1.1", 1114 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 1115 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 1116 | "dev": true 1117 | } 1118 | } 1119 | }, 1120 | "has-binary2": { 1121 | "version": "1.0.3", 1122 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 1123 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 1124 | "requires": { 1125 | "isarray": "2.0.1" 1126 | } 1127 | }, 1128 | "has-cors": { 1129 | "version": "1.1.0", 1130 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 1131 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" 1132 | }, 1133 | "has-flag": { 1134 | "version": "2.0.0", 1135 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 1136 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" 1137 | }, 1138 | "http-signature": { 1139 | "version": "1.2.0", 1140 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 1141 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 1142 | "requires": { 1143 | "assert-plus": "^1.0.0", 1144 | "jsprim": "^1.2.2", 1145 | "sshpk": "^1.7.0" 1146 | } 1147 | }, 1148 | "husky": { 1149 | "version": "0.14.3", 1150 | "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", 1151 | "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", 1152 | "dev": true, 1153 | "requires": { 1154 | "is-ci": "^1.0.10", 1155 | "normalize-path": "^1.0.0", 1156 | "strip-indent": "^2.0.0" 1157 | }, 1158 | "dependencies": { 1159 | "normalize-path": { 1160 | "version": "1.0.0", 1161 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", 1162 | "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=", 1163 | "dev": true 1164 | } 1165 | } 1166 | }, 1167 | "indent-string": { 1168 | "version": "2.1.0", 1169 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", 1170 | "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", 1171 | "dev": true, 1172 | "requires": { 1173 | "repeating": "^2.0.0" 1174 | } 1175 | }, 1176 | "indexof": { 1177 | "version": "0.0.1", 1178 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 1179 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" 1180 | }, 1181 | "inherits": { 1182 | "version": "2.0.3", 1183 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1184 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 1185 | }, 1186 | "is-arrayish": { 1187 | "version": "0.2.1", 1188 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1189 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 1190 | "dev": true 1191 | }, 1192 | "is-ci": { 1193 | "version": "1.0.10", 1194 | "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", 1195 | "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=", 1196 | "dev": true, 1197 | "requires": { 1198 | "ci-info": "^1.0.0" 1199 | } 1200 | }, 1201 | "is-finite": { 1202 | "version": "1.0.2", 1203 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", 1204 | "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", 1205 | "dev": true, 1206 | "requires": { 1207 | "number-is-nan": "^1.0.0" 1208 | } 1209 | }, 1210 | "is-fullwidth-code-point": { 1211 | "version": "2.0.0", 1212 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1213 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 1214 | }, 1215 | "is-obj": { 1216 | "version": "1.0.1", 1217 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", 1218 | "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", 1219 | "dev": true 1220 | }, 1221 | "is-promise": { 1222 | "version": "2.1.0", 1223 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 1224 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 1225 | "dev": true 1226 | }, 1227 | "is-regexp": { 1228 | "version": "1.0.0", 1229 | "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", 1230 | "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", 1231 | "dev": true 1232 | }, 1233 | "is-stream": { 1234 | "version": "1.1.0", 1235 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 1236 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 1237 | }, 1238 | "is-typedarray": { 1239 | "version": "1.0.0", 1240 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 1241 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 1242 | }, 1243 | "isarray": { 1244 | "version": "2.0.1", 1245 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 1246 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 1247 | }, 1248 | "isexe": { 1249 | "version": "2.0.0", 1250 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1251 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 1252 | }, 1253 | "isstream": { 1254 | "version": "0.1.2", 1255 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 1256 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 1257 | }, 1258 | "jest-get-type": { 1259 | "version": "21.2.0", 1260 | "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz", 1261 | "integrity": "sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==", 1262 | "dev": true 1263 | }, 1264 | "jest-validate": { 1265 | "version": "21.2.1", 1266 | "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-21.2.1.tgz", 1267 | "integrity": "sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg==", 1268 | "dev": true, 1269 | "requires": { 1270 | "chalk": "^2.0.1", 1271 | "jest-get-type": "^21.2.0", 1272 | "leven": "^2.1.0", 1273 | "pretty-format": "^21.2.1" 1274 | } 1275 | }, 1276 | "js-yaml": { 1277 | "version": "3.13.1", 1278 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 1279 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 1280 | "dev": true, 1281 | "requires": { 1282 | "argparse": "^1.0.7", 1283 | "esprima": "^4.0.0" 1284 | } 1285 | }, 1286 | "jsbn": { 1287 | "version": "0.1.1", 1288 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 1289 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 1290 | }, 1291 | "json-schema": { 1292 | "version": "0.2.3", 1293 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 1294 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 1295 | }, 1296 | "json-schema-traverse": { 1297 | "version": "0.4.1", 1298 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1299 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 1300 | }, 1301 | "json-stringify-safe": { 1302 | "version": "5.0.1", 1303 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 1304 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 1305 | }, 1306 | "jsprim": { 1307 | "version": "1.4.1", 1308 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 1309 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 1310 | "requires": { 1311 | "assert-plus": "1.0.0", 1312 | "extsprintf": "1.3.0", 1313 | "json-schema": "0.2.3", 1314 | "verror": "1.10.0" 1315 | } 1316 | }, 1317 | "leven": { 1318 | "version": "2.1.0", 1319 | "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", 1320 | "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", 1321 | "dev": true 1322 | }, 1323 | "lint-staged": { 1324 | "version": "4.2.3", 1325 | "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.2.3.tgz", 1326 | "integrity": "sha512-Ks1vMyVpp3ldeFDN9sIcHcFDh0v3X6Y6LOdT0Wl86/BSDM2R8PVcuFODkh0Dav7Ni/asUPKONfXRWZM9YO85IQ==", 1327 | "dev": true, 1328 | "requires": { 1329 | "app-root-path": "^2.0.0", 1330 | "chalk": "^2.1.0", 1331 | "cosmiconfig": "^1.1.0", 1332 | "execa": "^0.8.0", 1333 | "is-glob": "^4.0.0", 1334 | "jest-validate": "^21.1.0", 1335 | "listr": "^0.12.0", 1336 | "lodash": "^4.17.4", 1337 | "log-symbols": "^2.0.0", 1338 | "minimatch": "^3.0.0", 1339 | "npm-which": "^3.0.1", 1340 | "p-map": "^1.1.1", 1341 | "staged-git-files": "0.0.4", 1342 | "stringify-object": "^3.2.0" 1343 | }, 1344 | "dependencies": { 1345 | "execa": { 1346 | "version": "0.8.0", 1347 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", 1348 | "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", 1349 | "dev": true, 1350 | "requires": { 1351 | "cross-spawn": "^5.0.1", 1352 | "get-stream": "^3.0.0", 1353 | "is-stream": "^1.1.0", 1354 | "npm-run-path": "^2.0.0", 1355 | "p-finally": "^1.0.0", 1356 | "signal-exit": "^3.0.0", 1357 | "strip-eof": "^1.0.0" 1358 | } 1359 | }, 1360 | "is-extglob": { 1361 | "version": "2.1.1", 1362 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1363 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 1364 | "dev": true 1365 | }, 1366 | "is-glob": { 1367 | "version": "4.0.0", 1368 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", 1369 | "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", 1370 | "dev": true, 1371 | "requires": { 1372 | "is-extglob": "^2.1.1" 1373 | } 1374 | } 1375 | } 1376 | }, 1377 | "listr": { 1378 | "version": "0.12.0", 1379 | "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", 1380 | "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", 1381 | "dev": true, 1382 | "requires": { 1383 | "chalk": "^1.1.3", 1384 | "cli-truncate": "^0.2.1", 1385 | "figures": "^1.7.0", 1386 | "indent-string": "^2.1.0", 1387 | "is-promise": "^2.1.0", 1388 | "is-stream": "^1.1.0", 1389 | "listr-silent-renderer": "^1.1.1", 1390 | "listr-update-renderer": "^0.2.0", 1391 | "listr-verbose-renderer": "^0.4.0", 1392 | "log-symbols": "^1.0.2", 1393 | "log-update": "^1.0.2", 1394 | "ora": "^0.2.3", 1395 | "p-map": "^1.1.1", 1396 | "rxjs": "^5.0.0-beta.11", 1397 | "stream-to-observable": "^0.1.0", 1398 | "strip-ansi": "^3.0.1" 1399 | }, 1400 | "dependencies": { 1401 | "ansi-regex": { 1402 | "version": "2.1.1", 1403 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 1404 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 1405 | "dev": true 1406 | }, 1407 | "ansi-styles": { 1408 | "version": "2.2.1", 1409 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 1410 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 1411 | "dev": true 1412 | }, 1413 | "chalk": { 1414 | "version": "1.1.3", 1415 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 1416 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 1417 | "dev": true, 1418 | "requires": { 1419 | "ansi-styles": "^2.2.1", 1420 | "escape-string-regexp": "^1.0.2", 1421 | "has-ansi": "^2.0.0", 1422 | "strip-ansi": "^3.0.0", 1423 | "supports-color": "^2.0.0" 1424 | } 1425 | }, 1426 | "log-symbols": { 1427 | "version": "1.0.2", 1428 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", 1429 | "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", 1430 | "dev": true, 1431 | "requires": { 1432 | "chalk": "^1.0.0" 1433 | } 1434 | }, 1435 | "strip-ansi": { 1436 | "version": "3.0.1", 1437 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1438 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1439 | "dev": true, 1440 | "requires": { 1441 | "ansi-regex": "^2.0.0" 1442 | } 1443 | }, 1444 | "supports-color": { 1445 | "version": "2.0.0", 1446 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1447 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1448 | "dev": true 1449 | } 1450 | } 1451 | }, 1452 | "listr-silent-renderer": { 1453 | "version": "1.1.1", 1454 | "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", 1455 | "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", 1456 | "dev": true 1457 | }, 1458 | "listr-update-renderer": { 1459 | "version": "0.2.0", 1460 | "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", 1461 | "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", 1462 | "dev": true, 1463 | "requires": { 1464 | "chalk": "^1.1.3", 1465 | "cli-truncate": "^0.2.1", 1466 | "elegant-spinner": "^1.0.1", 1467 | "figures": "^1.7.0", 1468 | "indent-string": "^3.0.0", 1469 | "log-symbols": "^1.0.2", 1470 | "log-update": "^1.0.2", 1471 | "strip-ansi": "^3.0.1" 1472 | }, 1473 | "dependencies": { 1474 | "ansi-regex": { 1475 | "version": "2.1.1", 1476 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 1477 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 1478 | "dev": true 1479 | }, 1480 | "ansi-styles": { 1481 | "version": "2.2.1", 1482 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 1483 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 1484 | "dev": true 1485 | }, 1486 | "chalk": { 1487 | "version": "1.1.3", 1488 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 1489 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 1490 | "dev": true, 1491 | "requires": { 1492 | "ansi-styles": "^2.2.1", 1493 | "escape-string-regexp": "^1.0.2", 1494 | "has-ansi": "^2.0.0", 1495 | "strip-ansi": "^3.0.0", 1496 | "supports-color": "^2.0.0" 1497 | } 1498 | }, 1499 | "indent-string": { 1500 | "version": "3.2.0", 1501 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", 1502 | "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", 1503 | "dev": true 1504 | }, 1505 | "log-symbols": { 1506 | "version": "1.0.2", 1507 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", 1508 | "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", 1509 | "dev": true, 1510 | "requires": { 1511 | "chalk": "^1.0.0" 1512 | } 1513 | }, 1514 | "strip-ansi": { 1515 | "version": "3.0.1", 1516 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1517 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1518 | "dev": true, 1519 | "requires": { 1520 | "ansi-regex": "^2.0.0" 1521 | } 1522 | }, 1523 | "supports-color": { 1524 | "version": "2.0.0", 1525 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1526 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1527 | "dev": true 1528 | } 1529 | } 1530 | }, 1531 | "listr-verbose-renderer": { 1532 | "version": "0.4.0", 1533 | "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.0.tgz", 1534 | "integrity": "sha1-RNwBuww0oDxXIVTU0Izemx3FYg8=", 1535 | "dev": true, 1536 | "requires": { 1537 | "chalk": "^1.1.3", 1538 | "cli-cursor": "^1.0.2", 1539 | "date-fns": "^1.27.2", 1540 | "figures": "^1.7.0" 1541 | }, 1542 | "dependencies": { 1543 | "ansi-regex": { 1544 | "version": "2.1.1", 1545 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 1546 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 1547 | "dev": true 1548 | }, 1549 | "ansi-styles": { 1550 | "version": "2.2.1", 1551 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 1552 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 1553 | "dev": true 1554 | }, 1555 | "chalk": { 1556 | "version": "1.1.3", 1557 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 1558 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 1559 | "dev": true, 1560 | "requires": { 1561 | "ansi-styles": "^2.2.1", 1562 | "escape-string-regexp": "^1.0.2", 1563 | "has-ansi": "^2.0.0", 1564 | "strip-ansi": "^3.0.0", 1565 | "supports-color": "^2.0.0" 1566 | } 1567 | }, 1568 | "strip-ansi": { 1569 | "version": "3.0.1", 1570 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1571 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1572 | "dev": true, 1573 | "requires": { 1574 | "ansi-regex": "^2.0.0" 1575 | } 1576 | }, 1577 | "supports-color": { 1578 | "version": "2.0.0", 1579 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1580 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1581 | "dev": true 1582 | } 1583 | } 1584 | }, 1585 | "lodash": { 1586 | "version": "4.17.19", 1587 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", 1588 | "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" 1589 | }, 1590 | "log-symbols": { 1591 | "version": "2.1.0", 1592 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz", 1593 | "integrity": "sha512-zLeLrzMA1A2vRF1e/0Mo+LNINzi6jzBylHj5WqvQ/WK/5WCZt8si9SyN4p9llr/HRYvVR1AoXHRHl4WTHyQAzQ==", 1594 | "dev": true, 1595 | "requires": { 1596 | "chalk": "^2.0.1" 1597 | } 1598 | }, 1599 | "log-update": { 1600 | "version": "1.0.2", 1601 | "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", 1602 | "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", 1603 | "dev": true, 1604 | "requires": { 1605 | "ansi-escapes": "^1.0.0", 1606 | "cli-cursor": "^1.0.2" 1607 | }, 1608 | "dependencies": { 1609 | "ansi-escapes": { 1610 | "version": "1.4.0", 1611 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", 1612 | "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", 1613 | "dev": true 1614 | } 1615 | } 1616 | }, 1617 | "lru-cache": { 1618 | "version": "4.1.1", 1619 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", 1620 | "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=", 1621 | "requires": { 1622 | "pseudomap": "^1.0.2", 1623 | "yallist": "^2.1.2" 1624 | } 1625 | }, 1626 | "mime-db": { 1627 | "version": "1.40.0", 1628 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 1629 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 1630 | }, 1631 | "mime-types": { 1632 | "version": "2.1.24", 1633 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 1634 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 1635 | "requires": { 1636 | "mime-db": "1.40.0" 1637 | } 1638 | }, 1639 | "minimatch": { 1640 | "version": "3.0.4", 1641 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1642 | "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", 1643 | "dev": true, 1644 | "requires": { 1645 | "brace-expansion": "^1.1.7" 1646 | } 1647 | }, 1648 | "mkpath": { 1649 | "version": "0.1.0", 1650 | "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz", 1651 | "integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE=" 1652 | }, 1653 | "ms": { 1654 | "version": "2.1.1", 1655 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1656 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 1657 | }, 1658 | "negotiator": { 1659 | "version": "0.6.2", 1660 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1661 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1662 | }, 1663 | "ngrok": { 1664 | "version": "3.1.1", 1665 | "resolved": "https://registry.npmjs.org/ngrok/-/ngrok-3.1.1.tgz", 1666 | "integrity": "sha512-dCW/Ni12GRBL7XIyiFmilKOfCW7UVFf65I/HpE8FC5rXGJwdhIYLc9Qr05GRb6hNs6fZGwyLpcDLnDhUSgZasQ==", 1667 | "requires": { 1668 | "@types/node": "^8.10.30", 1669 | "decompress-zip": "^0.3.2", 1670 | "request": "^2.88.0", 1671 | "request-promise-native": "^1.0.5", 1672 | "uuid": "^3.3.2" 1673 | } 1674 | }, 1675 | "nopt": { 1676 | "version": "3.0.6", 1677 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", 1678 | "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", 1679 | "requires": { 1680 | "abbrev": "1" 1681 | } 1682 | }, 1683 | "npm-path": { 1684 | "version": "2.0.3", 1685 | "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz", 1686 | "integrity": "sha1-Fc/04ciaONp39W9gVbJPl137K74=", 1687 | "dev": true, 1688 | "requires": { 1689 | "which": "^1.2.10" 1690 | } 1691 | }, 1692 | "npm-run-path": { 1693 | "version": "2.0.2", 1694 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 1695 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 1696 | "requires": { 1697 | "path-key": "^2.0.0" 1698 | } 1699 | }, 1700 | "npm-which": { 1701 | "version": "3.0.1", 1702 | "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", 1703 | "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", 1704 | "dev": true, 1705 | "requires": { 1706 | "commander": "^2.9.0", 1707 | "npm-path": "^2.0.2", 1708 | "which": "^1.2.10" 1709 | } 1710 | }, 1711 | "number-is-nan": { 1712 | "version": "1.0.1", 1713 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1714 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1715 | }, 1716 | "oauth-sign": { 1717 | "version": "0.9.0", 1718 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 1719 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 1720 | }, 1721 | "object-assign": { 1722 | "version": "4.1.1", 1723 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1724 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1725 | "dev": true 1726 | }, 1727 | "object-component": { 1728 | "version": "0.0.3", 1729 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 1730 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" 1731 | }, 1732 | "onetime": { 1733 | "version": "1.1.0", 1734 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", 1735 | "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", 1736 | "dev": true 1737 | }, 1738 | "ora": { 1739 | "version": "0.2.3", 1740 | "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", 1741 | "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", 1742 | "dev": true, 1743 | "requires": { 1744 | "chalk": "^1.1.1", 1745 | "cli-cursor": "^1.0.2", 1746 | "cli-spinners": "^0.1.2", 1747 | "object-assign": "^4.0.1" 1748 | }, 1749 | "dependencies": { 1750 | "ansi-regex": { 1751 | "version": "2.1.1", 1752 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 1753 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 1754 | "dev": true 1755 | }, 1756 | "ansi-styles": { 1757 | "version": "2.2.1", 1758 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 1759 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 1760 | "dev": true 1761 | }, 1762 | "chalk": { 1763 | "version": "1.1.3", 1764 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 1765 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 1766 | "dev": true, 1767 | "requires": { 1768 | "ansi-styles": "^2.2.1", 1769 | "escape-string-regexp": "^1.0.2", 1770 | "has-ansi": "^2.0.0", 1771 | "strip-ansi": "^3.0.0", 1772 | "supports-color": "^2.0.0" 1773 | } 1774 | }, 1775 | "strip-ansi": { 1776 | "version": "3.0.1", 1777 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1778 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1779 | "dev": true, 1780 | "requires": { 1781 | "ansi-regex": "^2.0.0" 1782 | } 1783 | }, 1784 | "supports-color": { 1785 | "version": "2.0.0", 1786 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1787 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1788 | "dev": true 1789 | } 1790 | } 1791 | }, 1792 | "os-homedir": { 1793 | "version": "1.0.2", 1794 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1795 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 1796 | "dev": true 1797 | }, 1798 | "p-finally": { 1799 | "version": "1.0.0", 1800 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 1801 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" 1802 | }, 1803 | "p-map": { 1804 | "version": "1.2.0", 1805 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", 1806 | "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", 1807 | "dev": true 1808 | }, 1809 | "parse-json": { 1810 | "version": "2.2.0", 1811 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 1812 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 1813 | "dev": true, 1814 | "requires": { 1815 | "error-ex": "^1.2.0" 1816 | } 1817 | }, 1818 | "parseqs": { 1819 | "version": "0.0.5", 1820 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 1821 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 1822 | "requires": { 1823 | "better-assert": "~1.0.0" 1824 | } 1825 | }, 1826 | "parseuri": { 1827 | "version": "0.0.5", 1828 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 1829 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 1830 | "requires": { 1831 | "better-assert": "~1.0.0" 1832 | } 1833 | }, 1834 | "path-key": { 1835 | "version": "2.0.1", 1836 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1837 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" 1838 | }, 1839 | "performance-now": { 1840 | "version": "2.1.0", 1841 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1842 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 1843 | }, 1844 | "pinkie": { 1845 | "version": "2.0.4", 1846 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 1847 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 1848 | "dev": true 1849 | }, 1850 | "pinkie-promise": { 1851 | "version": "2.0.1", 1852 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 1853 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 1854 | "dev": true, 1855 | "requires": { 1856 | "pinkie": "^2.0.0" 1857 | } 1858 | }, 1859 | "prettier": { 1860 | "version": "1.7.4", 1861 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.7.4.tgz", 1862 | "integrity": "sha1-XoYkrpNjyA+V7GRFhOzfVddPk/o=", 1863 | "dev": true 1864 | }, 1865 | "pretty-format": { 1866 | "version": "21.2.1", 1867 | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz", 1868 | "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==", 1869 | "dev": true, 1870 | "requires": { 1871 | "ansi-regex": "^3.0.0", 1872 | "ansi-styles": "^3.2.0" 1873 | } 1874 | }, 1875 | "pseudomap": { 1876 | "version": "1.0.2", 1877 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 1878 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" 1879 | }, 1880 | "psl": { 1881 | "version": "1.1.32", 1882 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", 1883 | "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" 1884 | }, 1885 | "punycode": { 1886 | "version": "2.1.1", 1887 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1888 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 1889 | }, 1890 | "q": { 1891 | "version": "1.5.1", 1892 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 1893 | "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" 1894 | }, 1895 | "qs": { 1896 | "version": "6.5.2", 1897 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 1898 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 1899 | }, 1900 | "readable-stream": { 1901 | "version": "1.1.14", 1902 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 1903 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 1904 | "requires": { 1905 | "core-util-is": "~1.0.0", 1906 | "inherits": "~2.0.1", 1907 | "isarray": "0.0.1", 1908 | "string_decoder": "~0.10.x" 1909 | }, 1910 | "dependencies": { 1911 | "isarray": { 1912 | "version": "0.0.1", 1913 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 1914 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 1915 | } 1916 | } 1917 | }, 1918 | "repeating": { 1919 | "version": "2.0.1", 1920 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 1921 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 1922 | "dev": true, 1923 | "requires": { 1924 | "is-finite": "^1.0.0" 1925 | } 1926 | }, 1927 | "request": { 1928 | "version": "2.88.0", 1929 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 1930 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 1931 | "requires": { 1932 | "aws-sign2": "~0.7.0", 1933 | "aws4": "^1.8.0", 1934 | "caseless": "~0.12.0", 1935 | "combined-stream": "~1.0.6", 1936 | "extend": "~3.0.2", 1937 | "forever-agent": "~0.6.1", 1938 | "form-data": "~2.3.2", 1939 | "har-validator": "~5.1.0", 1940 | "http-signature": "~1.2.0", 1941 | "is-typedarray": "~1.0.0", 1942 | "isstream": "~0.1.2", 1943 | "json-stringify-safe": "~5.0.1", 1944 | "mime-types": "~2.1.19", 1945 | "oauth-sign": "~0.9.0", 1946 | "performance-now": "^2.1.0", 1947 | "qs": "~6.5.2", 1948 | "safe-buffer": "^5.1.2", 1949 | "tough-cookie": "~2.4.3", 1950 | "tunnel-agent": "^0.6.0", 1951 | "uuid": "^3.3.2" 1952 | } 1953 | }, 1954 | "request-promise-core": { 1955 | "version": "1.1.2", 1956 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", 1957 | "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", 1958 | "requires": { 1959 | "lodash": "^4.17.11" 1960 | } 1961 | }, 1962 | "request-promise-native": { 1963 | "version": "1.0.7", 1964 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", 1965 | "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", 1966 | "requires": { 1967 | "request-promise-core": "1.1.2", 1968 | "stealthy-require": "^1.1.1", 1969 | "tough-cookie": "^2.3.3" 1970 | } 1971 | }, 1972 | "require-from-string": { 1973 | "version": "1.2.1", 1974 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", 1975 | "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", 1976 | "dev": true 1977 | }, 1978 | "restore-cursor": { 1979 | "version": "1.0.1", 1980 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", 1981 | "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", 1982 | "dev": true, 1983 | "requires": { 1984 | "exit-hook": "^1.0.0", 1985 | "onetime": "^1.0.0" 1986 | } 1987 | }, 1988 | "rxjs": { 1989 | "version": "5.4.3", 1990 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz", 1991 | "integrity": "sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==", 1992 | "dev": true, 1993 | "requires": { 1994 | "symbol-observable": "^1.0.1" 1995 | } 1996 | }, 1997 | "safe-buffer": { 1998 | "version": "5.1.2", 1999 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 2000 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 2001 | }, 2002 | "safer-buffer": { 2003 | "version": "2.1.2", 2004 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 2005 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 2006 | }, 2007 | "shebang-command": { 2008 | "version": "1.2.0", 2009 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 2010 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 2011 | "requires": { 2012 | "shebang-regex": "^1.0.0" 2013 | } 2014 | }, 2015 | "shebang-regex": { 2016 | "version": "1.0.0", 2017 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 2018 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" 2019 | }, 2020 | "signal-exit": { 2021 | "version": "3.0.2", 2022 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 2023 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 2024 | }, 2025 | "slice-ansi": { 2026 | "version": "0.0.4", 2027 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", 2028 | "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", 2029 | "dev": true 2030 | }, 2031 | "socket.io": { 2032 | "version": "2.2.0", 2033 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz", 2034 | "integrity": "sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==", 2035 | "requires": { 2036 | "debug": "~4.1.0", 2037 | "engine.io": "~3.3.1", 2038 | "has-binary2": "~1.0.2", 2039 | "socket.io-adapter": "~1.1.0", 2040 | "socket.io-client": "2.2.0", 2041 | "socket.io-parser": "~3.3.0" 2042 | } 2043 | }, 2044 | "socket.io-adapter": { 2045 | "version": "1.1.1", 2046 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", 2047 | "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" 2048 | }, 2049 | "socket.io-client": { 2050 | "version": "2.2.0", 2051 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz", 2052 | "integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==", 2053 | "requires": { 2054 | "backo2": "1.0.2", 2055 | "base64-arraybuffer": "0.1.5", 2056 | "component-bind": "1.0.0", 2057 | "component-emitter": "1.2.1", 2058 | "debug": "~3.1.0", 2059 | "engine.io-client": "~3.3.1", 2060 | "has-binary2": "~1.0.2", 2061 | "has-cors": "1.1.0", 2062 | "indexof": "0.0.1", 2063 | "object-component": "0.0.3", 2064 | "parseqs": "0.0.5", 2065 | "parseuri": "0.0.5", 2066 | "socket.io-parser": "~3.3.0", 2067 | "to-array": "0.1.4" 2068 | }, 2069 | "dependencies": { 2070 | "debug": { 2071 | "version": "3.1.0", 2072 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 2073 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 2074 | "requires": { 2075 | "ms": "2.0.0" 2076 | } 2077 | }, 2078 | "ms": { 2079 | "version": "2.0.0", 2080 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 2081 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 2082 | } 2083 | } 2084 | }, 2085 | "socket.io-parser": { 2086 | "version": "3.3.0", 2087 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", 2088 | "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", 2089 | "requires": { 2090 | "component-emitter": "1.2.1", 2091 | "debug": "~3.1.0", 2092 | "isarray": "2.0.1" 2093 | }, 2094 | "dependencies": { 2095 | "debug": { 2096 | "version": "3.1.0", 2097 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 2098 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 2099 | "requires": { 2100 | "ms": "2.0.0" 2101 | } 2102 | }, 2103 | "ms": { 2104 | "version": "2.0.0", 2105 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 2106 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 2107 | } 2108 | } 2109 | }, 2110 | "sprintf-js": { 2111 | "version": "1.0.3", 2112 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 2113 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 2114 | "dev": true 2115 | }, 2116 | "sshpk": { 2117 | "version": "1.16.1", 2118 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 2119 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 2120 | "requires": { 2121 | "asn1": "~0.2.3", 2122 | "assert-plus": "^1.0.0", 2123 | "bcrypt-pbkdf": "^1.0.0", 2124 | "dashdash": "^1.12.0", 2125 | "ecc-jsbn": "~0.1.1", 2126 | "getpass": "^0.1.1", 2127 | "jsbn": "~0.1.0", 2128 | "safer-buffer": "^2.0.2", 2129 | "tweetnacl": "~0.14.0" 2130 | } 2131 | }, 2132 | "staged-git-files": { 2133 | "version": "0.0.4", 2134 | "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-0.0.4.tgz", 2135 | "integrity": "sha1-15fhtVHKemOd7AI33G60u5vhfTU=", 2136 | "dev": true 2137 | }, 2138 | "stealthy-require": { 2139 | "version": "1.1.1", 2140 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 2141 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 2142 | }, 2143 | "stream-to-observable": { 2144 | "version": "0.1.0", 2145 | "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", 2146 | "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=", 2147 | "dev": true 2148 | }, 2149 | "string-width": { 2150 | "version": "2.1.1", 2151 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 2152 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 2153 | "requires": { 2154 | "is-fullwidth-code-point": "^2.0.0", 2155 | "strip-ansi": "^4.0.0" 2156 | } 2157 | }, 2158 | "string_decoder": { 2159 | "version": "0.10.31", 2160 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 2161 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 2162 | }, 2163 | "stringify-object": { 2164 | "version": "3.2.1", 2165 | "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz", 2166 | "integrity": "sha512-jPcQYw/52HUPP8uOE4kkjxl5bB9LfHkKCTptIk3qw7ozP5XMIMlHMLjt00GGSwW6DJAf/njY5EU6Vpwl4LlBKQ==", 2167 | "dev": true, 2168 | "requires": { 2169 | "get-own-enumerable-property-symbols": "^2.0.1", 2170 | "is-obj": "^1.0.1", 2171 | "is-regexp": "^1.0.0" 2172 | } 2173 | }, 2174 | "strip-ansi": { 2175 | "version": "4.0.0", 2176 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 2177 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 2178 | "requires": { 2179 | "ansi-regex": "^3.0.0" 2180 | } 2181 | }, 2182 | "strip-eof": { 2183 | "version": "1.0.0", 2184 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 2185 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" 2186 | }, 2187 | "strip-indent": { 2188 | "version": "2.0.0", 2189 | "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", 2190 | "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", 2191 | "dev": true 2192 | }, 2193 | "stripe": { 2194 | "version": "4.25.0", 2195 | "resolved": "https://registry.npmjs.org/stripe/-/stripe-4.25.0.tgz", 2196 | "integrity": "sha512-sSRPSQ4BTSbdcevVSrtIJzlOCTIAXm8T38DE4zPL6ysYpIWGfIBdo2XnhouLK12/6cuLvaEInlfCZQgoEVzXpQ==", 2197 | "requires": { 2198 | "bluebird": "^2.10.2", 2199 | "lodash.isplainobject": "^4.0.6", 2200 | "object-assign": "^4.1.0", 2201 | "qs": "~6.0.4" 2202 | }, 2203 | "dependencies": { 2204 | "bluebird": { 2205 | "version": "2.11.0", 2206 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", 2207 | "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" 2208 | }, 2209 | "lodash.isplainobject": { 2210 | "version": "4.0.6", 2211 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 2212 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 2213 | }, 2214 | "object-assign": { 2215 | "version": "4.1.1", 2216 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 2217 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 2218 | }, 2219 | "qs": { 2220 | "version": "6.0.4", 2221 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.4.tgz", 2222 | "integrity": "sha1-UQGdhHIMk5uCc36EVWp4Izjs6ns=" 2223 | } 2224 | } 2225 | }, 2226 | "supports-color": { 2227 | "version": "4.4.0", 2228 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", 2229 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", 2230 | "requires": { 2231 | "has-flag": "^2.0.0" 2232 | } 2233 | }, 2234 | "symbol-observable": { 2235 | "version": "1.0.4", 2236 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", 2237 | "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=", 2238 | "dev": true 2239 | }, 2240 | "term-size": { 2241 | "version": "1.2.0", 2242 | "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", 2243 | "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", 2244 | "requires": { 2245 | "execa": "^0.7.0" 2246 | } 2247 | }, 2248 | "to-array": { 2249 | "version": "0.1.4", 2250 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 2251 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" 2252 | }, 2253 | "touch": { 2254 | "version": "0.0.3", 2255 | "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", 2256 | "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=", 2257 | "requires": { 2258 | "nopt": "~1.0.10" 2259 | }, 2260 | "dependencies": { 2261 | "nopt": { 2262 | "version": "1.0.10", 2263 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 2264 | "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", 2265 | "requires": { 2266 | "abbrev": "1" 2267 | } 2268 | } 2269 | } 2270 | }, 2271 | "tough-cookie": { 2272 | "version": "2.4.3", 2273 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 2274 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 2275 | "requires": { 2276 | "psl": "^1.1.24", 2277 | "punycode": "^1.4.1" 2278 | }, 2279 | "dependencies": { 2280 | "punycode": { 2281 | "version": "1.4.1", 2282 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 2283 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 2284 | } 2285 | } 2286 | }, 2287 | "traverse": { 2288 | "version": "0.3.9", 2289 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", 2290 | "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" 2291 | }, 2292 | "tunnel-agent": { 2293 | "version": "0.6.0", 2294 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 2295 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 2296 | "requires": { 2297 | "safe-buffer": "^5.0.1" 2298 | } 2299 | }, 2300 | "tweetnacl": { 2301 | "version": "0.14.5", 2302 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 2303 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 2304 | }, 2305 | "uri-js": { 2306 | "version": "4.2.2", 2307 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 2308 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 2309 | "requires": { 2310 | "punycode": "^2.1.0" 2311 | } 2312 | }, 2313 | "uuid": { 2314 | "version": "3.3.2", 2315 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 2316 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 2317 | }, 2318 | "verror": { 2319 | "version": "1.10.0", 2320 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 2321 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 2322 | "requires": { 2323 | "assert-plus": "^1.0.0", 2324 | "core-util-is": "1.0.2", 2325 | "extsprintf": "^1.2.0" 2326 | } 2327 | }, 2328 | "which": { 2329 | "version": "1.3.0", 2330 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", 2331 | "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", 2332 | "requires": { 2333 | "isexe": "^2.0.0" 2334 | } 2335 | }, 2336 | "widest-line": { 2337 | "version": "1.0.0", 2338 | "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", 2339 | "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", 2340 | "requires": { 2341 | "string-width": "^1.0.1" 2342 | }, 2343 | "dependencies": { 2344 | "ansi-regex": { 2345 | "version": "2.1.1", 2346 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 2347 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 2348 | }, 2349 | "is-fullwidth-code-point": { 2350 | "version": "1.0.0", 2351 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 2352 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 2353 | "requires": { 2354 | "number-is-nan": "^1.0.0" 2355 | } 2356 | }, 2357 | "string-width": { 2358 | "version": "1.0.2", 2359 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 2360 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 2361 | "requires": { 2362 | "code-point-at": "^1.0.0", 2363 | "is-fullwidth-code-point": "^1.0.0", 2364 | "strip-ansi": "^3.0.0" 2365 | } 2366 | }, 2367 | "strip-ansi": { 2368 | "version": "3.0.1", 2369 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 2370 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 2371 | "requires": { 2372 | "ansi-regex": "^2.0.0" 2373 | } 2374 | } 2375 | } 2376 | }, 2377 | "ws": { 2378 | "version": "6.1.4", 2379 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", 2380 | "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", 2381 | "requires": { 2382 | "async-limiter": "~1.0.0" 2383 | } 2384 | }, 2385 | "xmlhttprequest-ssl": { 2386 | "version": "1.5.5", 2387 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 2388 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" 2389 | }, 2390 | "yallist": { 2391 | "version": "2.1.2", 2392 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 2393 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" 2394 | }, 2395 | "yeast": { 2396 | "version": "0.1.2", 2397 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 2398 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" 2399 | } 2400 | } 2401 | } 2402 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stripe-webhook-monitor", 3 | "version": "1.0.0", 4 | "description": "Realtime monitor for Stripe webhooks.", 5 | "author": "Michael Glukhovsky ", 6 | "license": "MIT", 7 | "main": "server.js", 8 | "scripts": { 9 | "pretty": "prettier --write server.js public/app.js" 10 | }, 11 | "lint-staged": { 12 | "*.js": [ 13 | "npm run pretty", 14 | "git add" 15 | ] 16 | }, 17 | "devDependencies": { 18 | "husky": "^0.14.3", 19 | "lint-staged": "^4.2.3", 20 | "prettier": "^1.7.4" 21 | }, 22 | "dependencies": { 23 | "body-parser": "^1.17.2", 24 | "boxen": "^1.2.1", 25 | "express": "^4.15.3", 26 | "ngrok": "^3.1.1", 27 | "socket.io": "^2.2.0", 28 | "stripe": "^4.23.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/app.js: -------------------------------------------------------------------------------- 1 | Vue.use(VueRouter); 2 | Vue.config.devtools = true; 3 | 4 | const store = { 5 | events: [], 6 | eventTypes: [], 7 | eventColors: {}, 8 | eventCount: {}, 9 | socket: null, 10 | loading: true, 11 | pausedEvents: [], 12 | eventsPaused: false, 13 | dashboardUrl: null, 14 | filteredHidden: [], 15 | showingOptions: false, 16 | optionsSticky: false, 17 | // Event statistics 18 | stats: { 19 | // Track the number of events for each of the given event types 20 | numEvents: [], 21 | lastEventCount: {}, 22 | // Maximum number of samples we'll measure 23 | maxEventCount: 40, 24 | // Sampling interval (in ms) 25 | interval: 1500, 26 | ready: false 27 | }, 28 | colors: [ 29 | // stripe4 colors 30 | "#6b7c93", 31 | "#6772e5", 32 | "#3297d3", 33 | "#24b47e", 34 | "#e39f48", 35 | "#e37c4c", 36 | "#e25950", 37 | "#b76ac4", 38 | "#8f6ed5", 39 | // stripe6 colors 40 | "#aab7c4", 41 | "#87bbfd", 42 | "#68d4f8", 43 | "#74e4a2", 44 | "#fcd669", 45 | "#fdbc72", 46 | "#ffcca5", 47 | "#ffc7ee", 48 | "#cdd1f7" 49 | ] 50 | }; 51 | 52 | // Toggle a flag that pauses the event stream and starts queuing them silently 53 | store.togglePausedEvents = function togglePausedEvents() { 54 | if (store.eventsPaused) { 55 | store.events = store.pausedEvents.concat(store.events); 56 | store.eventsPaused = false; 57 | } else { 58 | store.eventsPaused = true; 59 | } 60 | }; 61 | 62 | // Add an event to the store 63 | store.addEvent = function addEvent(event) { 64 | // Track the event type (so we can filter by them) 65 | if (store.eventTypes.indexOf(event.type) === -1) { 66 | store.eventTypes.push(event.type); 67 | Vue.set(store.eventColors, event.type, store.colors.shift()); 68 | Vue.set(store.eventCount, event.type, 1); 69 | } else { 70 | let count = store.eventCount[event.type]; 71 | Vue.set(store.eventCount, event.type, count + 1); 72 | } 73 | // If the event stream is paused, add the event to a silent queue 74 | if (store.eventsPaused) { 75 | store.pausedEvents.unshift(event); 76 | } else { 77 | store.events.unshift(event); 78 | } 79 | }; 80 | 81 | // Get a human-readable version for Stripe event types 82 | store.humanEventType = function humanEventType(type) { 83 | // We'll derive a human-readable version from the machine-readable type, but 84 | // there are a few special cases. 85 | specialCases = { 86 | "account.external_account.created": "External account created", 87 | "account.external_account.updated": "External account updated", 88 | "account.external_account.deleted": "External account deleted", 89 | "charge.dispute.funds_reinstated": "Charge dispute: funds reinstated", 90 | "charge.dispute.funds_withdrawn": "Charge dispute: funds withdrawn", 91 | "invoiceitem.created": "Invoice item created", 92 | "invoiceitem.deleted": "Invoice item deleted", 93 | "invoiceitem.updated": "Invoice item updated", 94 | "sku.created": "SKU created", 95 | "sku.updated": "SKU updated", 96 | "sku.deleted": "SKU deleted" 97 | }; 98 | if (type in specialCases) { 99 | return specialCases[type]; 100 | } 101 | // Replace all periods and underscores with spaces 102 | let readable = type.replace(/(\.|_)/g, " "); 103 | // Capitalize 104 | return readable.charAt(0).toUpperCase() + readable.slice(1); 105 | }; 106 | 107 | store.recalculateStats = function recalculateStats() { 108 | let lastEventCount = store.stats.lastEventCount; 109 | let newEventCount = { 110 | timestamp: Date.now() 111 | }; 112 | 113 | Object.keys(store.eventCount).forEach(type => { 114 | // First time seeing this event type? Set its inital count to zero. 115 | if (!(type in lastEventCount)) { 116 | lastEventCount[type] = 0; 117 | store.fillStats(type); 118 | } 119 | // For each type, compute the difference 120 | newEventCount[type] = store.eventCount[type] - lastEventCount[type]; 121 | lastEventCount[type] = store.eventCount[type]; 122 | }); 123 | 124 | // Add the new data point, drop the last 125 | store.stats.numEvents.shift(); 126 | store.stats.numEvents.push(newEventCount); 127 | 128 | window.dispatchEvent(new Event("newStats")); 129 | return store.stats.numEvents; 130 | }; 131 | 132 | // Helper function to pad the statistics array with zero-counts for new events 133 | store.fillStats = function fillStats(eventType) { 134 | let arr = store.stats.numEvents; 135 | for (let i = 0; i < arr.length; i++) { 136 | arr[i][eventType] = 0; 137 | } 138 | }; 139 | 140 | // Start tracking statistics on webhooks (for use in visualizations, etc.) 141 | store.startTrackingStats = function startTrackingStats() { 142 | let timestamp = Date.now(); 143 | let stats = store.stats; 144 | 145 | // Prepare our statistics array with timestamps 146 | stats.numEvents = Array(stats.maxEventCount); 147 | for (let i = 0; i < stats.numEvents.length; i++) { 148 | stats.numEvents[i] = { 149 | timestamp: timestamp - (stats.numEvents.length - i) * store.stats.interval 150 | }; 151 | } 152 | 153 | // Fill our statistics arrays with default data (for each event type) 154 | for (let type in store.eventCount) { 155 | store.fillStats(type); 156 | } 157 | 158 | // Inform other components that we're now fully tracking statistics 159 | window.dispatchEvent(new Event("statsReady")); 160 | stats.ready = true; 161 | 162 | // Recalculate our statistics on a regular interval 163 | setInterval(store.recalculateStats, store.stats.interval); 164 | }; 165 | 166 | // Get basic information on our Stripe environment 167 | async function getStripeInfo() { 168 | try { 169 | let response = await fetch("/environment"); 170 | if (response.status === 200) { 171 | let stripeInfo = await response.json(); 172 | store.dashboardUrl = stripeInfo.dashboardUrl; 173 | } 174 | } catch (e) { 175 | console.log("Couldn't connect to backend:", e); 176 | } 177 | } 178 | 179 | // Subscribe to a realtime stream from the backend via Socket.io 180 | async function subscribeEvents() { 181 | // Fetch the most recent events from the server 182 | try { 183 | let response = await fetch("/recent-events"); 184 | if (response.status === 200) { 185 | let recentEvents = await response.json(); 186 | for (event of recentEvents) { 187 | store.addEvent(event); 188 | } 189 | } 190 | } catch (e) { 191 | console.log("Couldn't connect to backend:", e); 192 | } 193 | 194 | store.loading = false; 195 | // Subscribe to new events via Socket.io 196 | store.socket = io.connect(); 197 | // Whenever we receive an event via Socket.io, update our store 198 | store.socket.on("event", event => { 199 | if (event.type != "ping") { 200 | store.addEvent(event); 201 | } 202 | }); 203 | 204 | // Start tracking statistics on new events 205 | store.startTrackingStats(); 206 | } 207 | 208 | Vue.component("event", { 209 | props: ["event"], 210 | data() { 211 | return { 212 | store, 213 | showingJSON: false, 214 | showingMetadata: false, 215 | metadata: this.event.data.object.metadata 216 | }; 217 | }, 218 | computed: { 219 | // Generate a human-readable event type 220 | eventType() { 221 | return store.humanEventType(this.event.type); 222 | }, 223 | // Use a specific color for some types of events 224 | eventColor() { 225 | if (this.event.type in store.eventColors) { 226 | return "border-left-color: " + store.eventColors[this.event.type] + ";"; 227 | } 228 | }, 229 | // Whether this event has metadata 230 | hasMetadata() { 231 | // Only core objects (like Sources or Charges) have metadata 232 | if (!this.metadata) { 233 | return false; 234 | } 235 | return Object.keys(this.metadata).length > 0; 236 | }, 237 | // For some types of events, show a summary 238 | summary() { 239 | let evt = this.event.data.object; 240 | // Render an HTML link to the Stripe Dashboard 241 | const url = (route, text) => { 242 | return `${text}`; 243 | }; 244 | // Render a currency, take an amount (in USD, dollars) and a currency 245 | const currency = (amount, currency) => { 246 | return OSREC.CurrencyFormatter.format(amount, { currency }); 247 | }; 248 | // Render a date: takes a timestamp (milliseconds since epoch) 249 | const date = timestamp => moment(timestamp).format("MMMM Do, YYYY"); 250 | 251 | if (this.event.type === "account.external_account.created") { 252 | return `A new external account was created.`; 253 | } else if (this.event.type === "account.external_account.deleted") { 254 | return `A new external account was deleted.`; 255 | } else if (this.event.type === "account.external_account.updated") { 256 | return `A new external account was updated.`; 257 | } else if (this.event.type === "account.updated") { 258 | return `The Stripe account was updated.`; 259 | } else if (this.event.type === "balance.available") { 260 | return `The balance for this Stripe account was updated: 261 | ${currency( 262 | evt.available[0].amount / 100, 263 | evt.available.currency 264 | )} is available, 265 | ${currency( 266 | evt.pending[0].amount / 100, 267 | evt.pending.currency 268 | )} is pending.`; 269 | } else if (this.event.type === "charge.captured") { 270 | return `Customer ${url("customers/" + evt.customer, evt.customer)}'s' 271 | charge for ${currency(evt.amount / 100, evt.currency)} was 272 | ${url("charges/" + evt.id, "captured")}.`; 273 | } else if (this.event.type === "charge.dispute.closed") { 274 | return `The ${url("disputes/" + evt.id, "dispute")} for a 275 | ${url("charges/" + evt.charge, "charge")} was closed.`; 276 | } else if (this.event.type === "charge.dispute.created") { 277 | return `The ${url("disputes/" + evt.id, "dispute")} for a 278 | ${url("charges/" + evt.charge, "charge")} was created.`; 279 | } else if (this.event.type === "charge.dispute.funds_reinstated") { 280 | return `${currency( 281 | evt.amount / 100, 282 | evt.currency 283 | )} was reinstated to the 284 | Stripe account following a ${url("disputes/" + evt.id, "dispute")}.`; 285 | } else if (this.event.type === "charge.dispute.funds_withdrawn") { 286 | return `${currency( 287 | evt.amount / 100, 288 | evt.currency 289 | )} was withdrawn from the 290 | Stripe account following a ${url("disputes/" + evt.id, "dispute")}.`; 291 | } else if (this.event.type === "charge.dispute.updated") { 292 | return `The ${url("disputes/" + evt.id, "dispute")} for a 293 | ${url("charges/" + evt.charge, "charge")} was updated.`; 294 | } else if (this.event.type === "charge.failed") { 295 | return `A recent ${url("charges/" + evt.id, "charge")} for 296 | ${currency(evt.amount / 100, evt.currency)} failed.`; 297 | } else if (this.event.type === "charge.pending") { 298 | return `A recent ${url("charges/" + evt.id, "charge")} for 299 | ${currency(evt.amount / 100, evt.currency)} is pending.`; 300 | } else if (this.event.type === "charge.refund.updated") { 301 | return `A ${currency(evt.amount / 100, evt.currency)} refund for a 302 | ${url("charges/" + evt.id, "charge")} was updated.`; 303 | } else if (this.event.type === "charge.refunded") { 304 | return `A ${currency(evt.amount / 100, evt.currency)} 305 | ${url("charges/" + evt.id, "charge")} was refunded.`; 306 | } else if (this.event.type === "charge.succeeded") { 307 | return `A ${url("customers/" + evt.customer, "customer")} 308 | was charged ${currency(evt.amount / 100, evt.currency)} 309 | with a ${evt.source.brand} ${evt.source.funding} ${evt.source 310 | .object}.`; 311 | } else if (this.event.type === "charge.updated") { 312 | return `A ${currency(evt.amount / 100, evt.currency)} 313 | ${url("charges/" + evt.id, "charge")} was updated.`; 314 | } else if (this.event.type === "coupon.created") { 315 | return `A coupon was created.`; 316 | } else if (this.event.type === "coupon.deleted") { 317 | return `A coupon was deleted.`; 318 | } else if (this.event.type === "coupon.updated") { 319 | return `A coupon was updated.`; 320 | } else if (this.event.type === "customer.bank_account.deleted") { 321 | return `A ${url("customers/" + evt.id, "customer")}'s bank account was 322 | deleted.`; 323 | } else if (this.event.type === "customer.created") { 324 | return `A ${url("customers/" + evt.id, "new customer")} 325 | ${evt.email ? "(" + evt.email + ")" : ""} was created.`; 326 | } else if (this.event.type === "customer.deleted") { 327 | return `A ${url("customers/" + evt.id, " customer")} 328 | ${evt.email ? "(" + evt.email + ")" : ""} was deleted.`; 329 | } else if (this.event.type === "customer.discount.created") { 330 | return `A discount for a ${url("customers/" + evt.id, "customer")} was 331 | created.`; 332 | } else if (this.event.type === "customer.discount.deleted") { 333 | return `A discount for a ${url("customers/" + evt.id, "customer")} was 334 | deleted.`; 335 | } else if (this.event.type === "customer.discount.updated") { 336 | return `A discount for a ${url("customers/" + evt.id, "customer")} was 337 | updated.`; 338 | } else if (this.event.type === "customer.source.created") { 339 | return `A ${url("customers/" + evt.customer, "customer")} added a new 340 | payment source.`; 341 | } else if (this.event.type === "customer.source.deleted") { 342 | return `A ${url("customers/" + evt.customer, "customer")} deleted a 343 | payment source.`; 344 | } else if (this.event.type === "customer.source.updated") { 345 | return `A ${url("customers/" + evt.customer, "customer")} updated a 346 | payment source.`; 347 | } else if (this.event.type === "customer.subscription.created") { 348 | return `A ${url("customers/" + evt.customer, "customer")} 349 | created a new ${url("subscriptions/" + evt.id, "subscription")} to the 350 | ${url("plans/" + evt.plan.id, evt.plan.name)} plan.`; 351 | } else if (this.event.type === "customer.subscription.deleted") { 352 | return `A ${url("customers/" + evt.customer, "customer")} 353 | deleted a ${url("subscriptions/" + evt.id, "subscription")} to the 354 | ${url("plans/" + evt.plan.id, evt.plan.name)} plan.`; 355 | } else if (this.event.type === "customer.subscription.trial_will_end") { 356 | return `A ${url("customers/" + evt.customer, "customer")}'s trial 357 | ${url("subscriptions/" + evt.id, "subscription")} will end on 358 | ${date(evt.trial_end)}.`; 359 | } else if (this.event.type === "customer.subscription.updated") { 360 | return `A ${url("customers/" + evt.customer, "customer")}'s 361 | ${url("subscriptions/" + evt.id, "subscription")} was updated.`; 362 | } else if (this.event.type === "customer.updated") { 363 | return `A ${url("customers/" + evt.customer, "customer")} was updated.`; 364 | } else if (this.event.type === "file.created") { 365 | return `A new file was uploded.`; 366 | } else if (this.event.type === "invoice.created") { 367 | return `A ${url("customers/" + evt.customer, "customer")}'s 368 | ${url("invoices/" + evt.id, "invoice")} was created.`; 369 | } else if (this.event.type === "invoice.payment_failed") { 370 | return `A ${url("customers/" + evt.customer, "customer")}'s 371 | ${url("invoices/" + evt.id, "invoice")} invoice payment failed.`; 372 | } else if (this.event.type === "invoice.payment_succeeded") { 373 | return `A ${url("customers/" + evt.customer, "customer")}'s 374 | ${url("invoices/" + evt.id, "invoice")} was successfully charged.`; 375 | } else if (this.event.type === "invoice.sent") { 376 | return `A ${url("customers/" + evt.customer, "customer")}'s 377 | ${url("invoices/" + evt.id, "invoice")} was sent.`; 378 | } else if (this.event.type === "invoice.upcoming") { 379 | return `A ${url("customers/" + evt.customer, "customer")}'s 380 | ${url("invoices/" + evt.id, "invoice")} was updated.`; 381 | } else if (this.event.type === "invoice.updated") { 382 | return `A ${url("customers/" + evt.customer, "customer")}'s 383 | ${url("invoices/" + evt.id, "invoice")} was updated.`; 384 | } else if (this.event.type === "invoiceitem.created") { 385 | return `A ${url( 386 | "customers/" + evt.customer, 387 | "customer" 388 | )} created an invoice 389 | item${evt.invoice 390 | ? " for an " + url("invoices/" + evt.invoice, "invoice") 391 | : ""}.`; 392 | } else if (this.event.type === "invoiceitem.deleted") { 393 | return `A ${url( 394 | "customers/" + evt.customer, 395 | "customer" 396 | )} deleted an invoice 397 | item${evt.invoice 398 | ? " for an " + url("invoices/" + evt.invoice, "invoice") 399 | : ""}.`; 400 | } else if (this.event.type === "invoiceitem.updated") { 401 | return `A ${url( 402 | "customers/" + evt.customer, 403 | "customer" 404 | )} updated an invoice 405 | item${evt.invoice 406 | ? " for an " + url("invoices/" + evt.invoice, "invoice") 407 | : ""}.`; 408 | } else if (this.event.type === "order.created") { 409 | return `A new ${url("orders/" + evt.id, "order")} for 410 | ${currency(evt.amount / 100)} was created.`; 411 | } else if (this.event.type === "order.payment_failed") { 412 | return `A payment failed for an ${url("orders/" + evt.id, "order")} for 413 | ${currency(evt.amount / 100)}.`; 414 | } else if (this.event.type === "order.payment_succeeded") { 415 | return `A payment succeeded for an ${url( 416 | "orders/" + evt.id, 417 | "order" 418 | )} for 419 | ${currency(evt.amount / 100)}.`; 420 | } else if (this.event.type === "order.updated") { 421 | return `An ${url("orders/" + evt.id, "order")} for 422 | ${currency(evt.amount / 100)} was updated.`; 423 | } else if (this.event.type === "order_return.created") { 424 | return `A return was created for an ${url( 425 | "orders/" + evt.order, 426 | "order" 427 | )} 428 | for ${currency(evt.amount / 100)}.`; 429 | } else if (this.event.type === "payout.canceled") { 430 | return `A payout of ${currency(evt.amount / 100)} was canceled.`; 431 | } else if (this.event.type === "payout.created") { 432 | return `A payout of ${currency(evt.amount / 100)} was initiated.`; 433 | } else if (this.event.type === "payout.failed") { 434 | return `A payout of ${currency(evt.amount / 100)} was failed.`; 435 | } else if (this.event.type === "payout.paid") { 436 | return `A payout of ${currency(evt.amount / 100)} was paid.`; 437 | } else if (this.event.type === "payout.updated") { 438 | return `A payout of ${currency(evt.amount / 100)} was updated.`; 439 | } else if (this.event.type === "plan.created") { 440 | return `Plan ${url("plans/" + evt.id, evt.name)} was created.`; 441 | } else if (this.event.type === "plan.deleted") { 442 | return `Plan ${evt.name} was deleted.`; 443 | } else if (this.event.type === "plan.updated") { 444 | return `Plan ${evt.name} was updated.`; 445 | } else if (this.event.type === "product.created") { 446 | return `A new ${url("products/" + evt.id, "product")} was created.`; 447 | } else if (this.event.type === "product.deleted") { 448 | return `A ${url("products/" + evt.id, "product")} was deleted.`; 449 | } else if (this.event.type === "product.updated") { 450 | return `A ${url("products/" + evt.id, "product")} was updated.`; 451 | } else if (this.event.type === "recipient.created") { 452 | return `A new recipient was created.`; 453 | } else if (this.event.type === "recipient.deleted") { 454 | return `A new recipient was deleted.`; 455 | } else if (this.event.type === "recipient.updated") { 456 | return `A new recipient was updated.`; 457 | } else if (this.event.type === "review.closed") { 458 | return `A fraud review was closed.`; 459 | } else if (this.event.type === "review.opened") { 460 | return `A fraud review was opened.`; 461 | } else if (this.event.type === "sku.created") { 462 | return `A ${url( 463 | "products/" + evt.product, 464 | "product" 465 | )} SKU was created.`; 466 | } else if (this.event.type === "sku.deleted") { 467 | return `A ${url( 468 | "products/" + evt.product, 469 | "product" 470 | )} SKU was deleted.`; 471 | } else if (this.event.type === "sku.updated") { 472 | return `A ${url( 473 | "products/" + evt.product, 474 | "product" 475 | )} SKU was updated.`; 476 | } else if (this.event.type === "source.canceled") { 477 | return `A payment source was canceled.`; 478 | } else if (this.event.type === "source.chargeable") { 479 | return `A payment source is now chargeable.`; 480 | } else if (this.event.type === "source.failed") { 481 | return `A payment source failed.`; 482 | } else if (this.event.type === "source.transaction_created") { 483 | return `A transaction was created for a payment source.`; 484 | } else if (this.event.type === "transfer.created") { 485 | return `A transfer was created.`; 486 | } else if (this.event.type === "transfer.reversed") { 487 | return `A transfer was reversed.`; 488 | } else if (this.event.type === "transfer.updated") { 489 | return `A transfer was updated.`; 490 | } 491 | } 492 | }, 493 | filters: { 494 | timeAgo: created => moment(created * 1000).fromNow() 495 | }, 496 | methods: { 497 | viewDashboard() { 498 | window.open(`${store.dashboardUrl}events/${this.event.id}`, "_blank"); 499 | } 500 | }, 501 | template: ` 502 | 503 |
504 |
505 |

{{ eventType }}

506 |

{{ event.created | timeAgo }}

507 |
508 |
509 |
510 |
511 |

512 |

{{ this.event.data.object.description }}

513 |
514 |
515 | 516 |
517 |
518 |
519 |

520 | 521 | HideInspect JSON 522 |

523 | 524 |
525 |
526 |

527 | 528 | HideShow metadata 529 |

530 | 531 |
532 |
533 |
534 |
535 | ` 536 | }); 537 | 538 | Vue.component("eventJSON", { 539 | props: ["json"], 540 | data() { 541 | return { 542 | // Tuples of regular expressions for Stripe resources (IDs, charges, customers) and the Stripe dashboard resource URL 543 | knownUrls: [ 544 | [/(")(evt_.+)(")/g, `${store.dashboardUrl}events/`], 545 | [/(")(cus_.+)(")/g, `${store.dashboardUrl}customers/`], 546 | [/(")(ch_.+)(")/g, `${store.dashboardUrl}charges/`], 547 | [/(")(dp.+)(")/g, `${store.dashboardUrl}disputes/`], 548 | [/(")(in.+)(")/g, `${store.dashboardUrl}invoices/`] 549 | ] 550 | }; 551 | }, 552 | mounted() { 553 | // Use Prism to add syntax highlighting 554 | Prism.highlightElement(this.$refs.code); 555 | // Highlight links in our webhook event's JSON payload 556 | let $strings = this.$refs.code.querySelectorAll("span.token.string"); 557 | // Look at each string token 558 | for (const $string of $strings) { 559 | // See if we recognize the token and can add a link to the Dashboard 560 | for (let i = 0; i < this.knownUrls.length; i++) { 561 | const regex = this.knownUrls[i][0]; 562 | const url = this.knownUrls[i][1]; 563 | const match = regex.exec($string.innerText); 564 | if (match && match.length === 4) { 565 | // Wrap the token in a link tag 566 | $string.innerHTML = `${match[1]}${match[2]}${match[3]}`; 567 | break; 568 | } 569 | } 570 | } 571 | }, 572 | filters: { 573 | prettify: json => JSON.stringify(json, null, 2) 574 | }, 575 | template: ` 576 |
{{ json | prettify }}
577 | ` 578 | }); 579 | 580 | Vue.component("logs", { 581 | data() { 582 | return { store }; 583 | }, 584 | template: ` 585 |
586 |
587 | 588 |
  • 589 | 590 |
  • 591 |
    592 |

    Loading...

    593 |

    No recent events.

    594 |
    595 | 596 |
    597 | ` 598 | }); 599 | 600 | Vue.component("charts", { 601 | data() { 602 | return { 603 | store, 604 | graph: null, 605 | data: [], 606 | month: new Date(2014, 0, 1), 607 | ticking: false, 608 | filtering: false, 609 | transitioning: false, 610 | plotted: false, 611 | animationSpeed: 150, 612 | easing: d3.easeCubicIn, 613 | listeners: {}, 614 | // Selections to create and remove before and after transitions 615 | removeQueue: null, 616 | createQueue: null, 617 | // Current translation offset of elements that are drawn on the page 618 | translateOffset: 0, 619 | plot: { 620 | x: null, 621 | y: null, 622 | svg: null, 623 | area: null, 624 | xAxisGroup: null, 625 | yAxisGroup: null, 626 | xAxis: null, 627 | yAxis: null, 628 | stack: null, 629 | layer: null, 630 | path: null, 631 | data: null 632 | } 633 | }; 634 | }, 635 | methods: { 636 | xDomain(data) { 637 | let lastDate = new Date(data[data.length - 2].timestamp); 638 | let firstDate = new Date(data[0].timestamp); 639 | return [firstDate, lastDate]; 640 | }, 641 | yDomain(data) { 642 | // Get the maximum of the total number of event types 643 | let max = d3.max(data, d => { 644 | // Sum the values for each event type 645 | return d3.sum(this.keys(), key => d[key]) * 1.2; 646 | }); 647 | return [0, max]; 648 | }, 649 | keys() { 650 | return store.eventTypes.filter( 651 | key => store.filteredHidden.indexOf(key) < 0 652 | ); 653 | }, 654 | // Visually update the areas of the stacked graph 655 | updateAreas(selection) { 656 | selection.attr("d", this.plot.area); 657 | if (this.createQueue) { 658 | this.createQueue.attr("d", this.plot.area); 659 | this.createQueue = null; 660 | } 661 | if (this.removeQueue) { 662 | this.removeQueue.remove(); 663 | this.removeQueue = null; 664 | } 665 | }, 666 | // Animate the areas of the stacked graph 667 | animateAreas(selection) { 668 | // Calculate how far we'll want to slide each area: 669 | // in this case, one time interval on the plot 670 | const previousStatement = new Date( 671 | this.plot.data[0].timestamp - store.stats.interval 672 | ); 673 | const translateOffset = this.plot.x(previousStatement); 674 | // Set the actual offset to zero at the start of the transition 675 | this.translateOffset = 0; 676 | // Set the `transitioning` state if there are elements to transition 677 | if (!selection.empty()) { 678 | this.transitioning = true; 679 | } 680 | 681 | selection 682 | // Set the original translate offset to zero 683 | .attr("transform", "translate(0)") 684 | .transition() 685 | // Slide each area across the plot 686 | .duration(this.animationSpeed) 687 | .ease(this.easing) 688 | .attr("transform", `translate(${translateOffset})`) 689 | .on("end", () => { 690 | this.translateOffset = translateOffset; 691 | this.transitioning = false; 692 | this.filtering = false; 693 | }); 694 | }, 695 | updateGraph() { 696 | // If the event stream is paused, don't update the graph 697 | if (store.eventsPaused || document.hidden) { 698 | return; 699 | } 700 | // Define the animation behavior (speed and easing) 701 | const plot = this.plot; 702 | const data = store.stats.numEvents; 703 | plot.data = data; 704 | 705 | // Use a stack layout to create our stacked area graph 706 | plot.stack = d3 707 | .stack() 708 | .keys(this.keys()) 709 | .order(d3.stackOrderNone) 710 | .offset(d3.stackOffsetNone); 711 | 712 | // Bind the data to the layer 713 | const layer = plot.svg 714 | .selectAll(".layer") 715 | .data(plot.stack(data), d => d.key); 716 | 717 | // Recalculate the domains of each axis 718 | plot.x.domain(this.xDomain(data)); 719 | plot.y.domain(this.yDomain(data)); 720 | 721 | // Update the axes 722 | plot.xAxisGroup 723 | .transition() 724 | .duration(this.animationSpeed) 725 | .ease(this.easing) 726 | .call(plot.xAxis); 727 | plot.yAxisGroup 728 | .transition() 729 | .duration(this.animationSpeed) 730 | .ease(this.easing) 731 | .call(plot.yAxis); 732 | 733 | // Enter new elements 734 | layer 735 | .enter() 736 | .append("g") 737 | .attr("class", d => `layer ${d.key}`) 738 | .attr("clip-path", "url(#clip)") 739 | .append("path") 740 | // Set up paths for new elements (but don't actually plot them yet) 741 | .attr("class", d => `area ${d.key}`) 742 | .style("fill", (d, i) => store.eventColors[d.key]) 743 | .call(selection => { 744 | // If we're not in the middle of a transition, visually update the plot 745 | if (!this.transitioning) { 746 | selection 747 | .attr("d", plot.area) 748 | .attr("transform", `translate(${this.translateOffset})`); 749 | } else { 750 | // Otherwise, add it to a creation queue 751 | this.createQueue = selection; 752 | } 753 | }); 754 | 755 | // Remove exited elements 756 | layer.exit().call(selection => { 757 | this.removeQueue = selection; 758 | }); 759 | 760 | // Visually update the plot (when we're not filtering or transitioning) 761 | if (!this.transitioning && !this.filtering) { 762 | layer 763 | .select(".area") 764 | .call(this.updateAreas) 765 | .call(this.animateAreas); 766 | } else { 767 | // If we've just filtered our data: 768 | if (this.filtering) { 769 | // ...and we're not in the middle of a transition, visually update the plot 770 | if (!this.transitioning) { 771 | layer.select(".area").call(this.updateAreas); 772 | } 773 | // Otherwise, skip this update cycle (but make sure we update + 774 | // animate the plot on the next cycle) 775 | this.filtering = false; 776 | } 777 | } 778 | }, 779 | drawGraph() { 780 | const plot = this.plot; 781 | const data = store.recalculateStats(); 782 | plot.data = data; 783 | 784 | let $recentEvents = this.$refs.recentEvents; 785 | // Build the SVG plot 786 | const padding = { horizontal: 20, vertical: 20 }, 787 | width = $recentEvents.offsetWidth - padding.horizontal, 788 | height = $recentEvents.offsetHeight - padding.vertical; 789 | 790 | plot.x = d3 791 | .scaleTime() 792 | // Find the largest and smallest dates for the domain 793 | .domain(this.xDomain(data)) 794 | .range([0, width]); 795 | plot.y = d3 796 | .scaleLinear() 797 | // Set the domain to the highest event count 798 | .domain(this.yDomain(data)) 799 | .range([height, 0]); 800 | 801 | plot.svg = d3 802 | .select("svg") 803 | .attr("width", width) 804 | .attr("height", height) 805 | .append("g") 806 | .attr("width", width) 807 | .attr("height", height) 808 | .attr( 809 | "transform", 810 | `translate(${padding.vertical},-${padding.horizontal})` 811 | ); 812 | 813 | plot.svg 814 | .append("defs") 815 | .append("clipPath") 816 | .attr("id", "clip") 817 | .append("rect") 818 | .attr("width", width) 819 | .attr("height", height); 820 | 821 | // Build our area graph 822 | plot.area = d3 823 | .area() 824 | // The x axis is based on time 825 | .x((d, i) => plot.x(new Date(d.data.timestamp))) 826 | // y0 and y1 represent the bottom and top values for each section of the area graph 827 | .y0(d => plot.y(d[0])) 828 | .y1(d => plot.y(d[1])) 829 | .curve(d3.curveBasis); 830 | 831 | // Define the visual axes on the plot 832 | plot.xAxis = d3 833 | .axisBottom() 834 | .scale(plot.x) 835 | .tickFormat(d3.timeFormat("%H:%M:%S")); 836 | plot.yAxis = d3.axisLeft().scale(plot.y); 837 | 838 | // Add our axes to the plot 839 | plot.xAxisGroup = plot.svg 840 | .append("g") 841 | .attr("class", "x axis") 842 | .attr("transform", `translate(0, ${height})`) 843 | .attr("clip-path", "url(#clip)") 844 | .call(plot.xAxis); 845 | plot.yAxisGroup = plot.svg 846 | .append("g") 847 | .attr("class", "y axis") 848 | .call(plot.yAxis); 849 | 850 | // Update the graph to use our data set: we've now fully plotted the graph 851 | this.updateGraph(data); 852 | this.plotted = true; 853 | } 854 | }, 855 | destroyed() { 856 | // Remove all of the event listeners on `window` 857 | window.removeEventListener("statsReady", this.drawGraph); 858 | window.removeEventListener("newStats", this.listeners.newStats); 859 | window.removeEventListener("filteredType", this.listeners.filteredType); 860 | window.removeEventListener("resize", this.listeners.resize); 861 | }, 862 | 863 | mounted() { 864 | // Wait for stats to be ready before drawing the graph 865 | if (store.stats.ready) { 866 | this.drawGraph(); 867 | } else { 868 | // Draw the graph once statistics are set up 869 | window.addEventListener("statsReady", this.drawGraph); 870 | } 871 | 872 | // Tick the graph whenever new stats are available 873 | this.listeners.newStats = () => { 874 | if (this.plotted) { 875 | this.updateGraph(); 876 | } 877 | }; 878 | window.addEventListener("newStats", this.listeners.newStats); 879 | 880 | // Visually update the graph whenever data is filtered (by event type) 881 | this.listeners.filteredType = () => { 882 | if (this.plotted) { 883 | this.filtering = true; 884 | this.updateGraph(); 885 | } 886 | }; 887 | window.addEventListener("filteredType", this.listeners.filteredType); 888 | 889 | // Redraw the graph after the window has been resized 890 | let onResize; 891 | this.listeners.resize = () => { 892 | clearTimeout(onResize); 893 | onResize = setTimeout(() => { 894 | const svg = d3.select("svg"); 895 | svg 896 | .attr("width", null) 897 | .attr("height", null) 898 | .selectAll("*") 899 | .remove(); 900 | 901 | this.drawGraph(); 902 | }, 200); 903 | }; 904 | window.addEventListener("resize", this.listeners.resize); 905 | }, 906 | template: ` 907 |
    908 |
    909 |

    Recent webhook events

    910 |
    911 | 912 |
    913 |
    914 | 915 |
    916 | ` 917 | }); 918 | 919 | Vue.component("monitor-options", { 920 | data() { 921 | return { 922 | store 923 | }; 924 | }, 925 | mounted() { 926 | const waypoint = new Waypoint({ 927 | element: this.$refs.monitorOptions, 928 | handler: direction => { 929 | if (direction == "down") { 930 | store.optionsSticky = true; 931 | } else { 932 | store.optionsSticky = false; 933 | } 934 | } 935 | }); 936 | }, 937 | methods: { 938 | toggleType(type) { 939 | let index = store.filteredHidden.indexOf(type); 940 | if (index > -1) { 941 | store.filteredHidden.splice(index, 1); 942 | } else { 943 | store.filteredHidden.push(type); 944 | } 945 | window.dispatchEvent(new Event("filteredType")); 946 | }, 947 | colorize(type) { 948 | return store.eventColors[type]; 949 | } 950 | }, 951 | template: ` 952 | 971 | ` 972 | }); 973 | 974 | const router = new VueRouter({ 975 | //base: window.location.href, 976 | routes: [ 977 | { name: "logs", path: "/", component: Vue.component("logs") }, 978 | { name: "charts", path: "/charts", component: Vue.component("charts") } 979 | ] 980 | }); 981 | 982 | const app = new Vue({ 983 | el: "#app", 984 | router: router, 985 | data() { 986 | return { store }; 987 | }, 988 | created() { 989 | // Gather info on our Stripe account and start our subscription 990 | getStripeInfo(); 991 | subscribeEvents(); 992 | }, 993 | template: ` 994 |
    995 |
    996 | 1001 | 1011 | 1018 |
    1019 | 1020 |
    1021 | ` 1022 | }); 1023 | 1024 | window.app = app; 1025 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Stripe Monitor 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin: 0; 4 | color: rgb(51, 51, 51); 5 | height: 100%; 6 | } 7 | 8 | button { 9 | border-radius: 3px; 10 | vertical-align: middle; 11 | cursor: pointer; 12 | height: 35px; 13 | background: transparent; 14 | border: 2px solid #87bbfd; 15 | color: rgb(16, 39, 54); 16 | font-size: 14px; 17 | padding: 0 12px; 18 | } 19 | 20 | button i.icon { 21 | display: inline-block; 22 | margin-right:7px; 23 | } 24 | 25 | button i.icon.icon-right { 26 | margin-left: 7px; 27 | margin-right: 0; 28 | } 29 | 30 | button:focus { 31 | outline: 0; 32 | } 33 | 34 | button:hover, button.active { 35 | border-color: #6772e5; 36 | background: #6772e5; 37 | color: #ffffff; 38 | } 39 | 40 | #app { 41 | height: 100%; 42 | display: flex; 43 | flex-direction: column; 44 | overflow-x: hidden; 45 | } 46 | 47 | #app .fill-view { 48 | flex-grow: 1; 49 | } 50 | 51 | #app .has-options { 52 | display: flex; 53 | height: 100%; 54 | } 55 | 56 | /* Navigation header */ 57 | .app-header { 58 | background: #424770; 59 | color: white; 60 | padding: 0 20px; 61 | display: flex; 62 | flex-direction: row; 63 | flex-shrink: 0; 64 | } 65 | 66 | .app-header nav { 67 | flex-grow: 1; 68 | display: flex; 69 | align-items: flex-end; 70 | } 71 | 72 | .app-header nav h1, h2 { 73 | padding: 15px 8px 10px 8px; 74 | margin: 0; 75 | margin-right: 10px; 76 | border-bottom: 5px solid transparent; 77 | font-weight: normal; 78 | } 79 | 80 | .app-header h1 { 81 | font-size: 22px; 82 | } 83 | 84 | .app-header h2 { 85 | font-size: 18px; 86 | } 87 | 88 | .app-header h2.router-link-active { 89 | border-bottom-color: #beb0f4; 90 | } 91 | 92 | .app-header a { 93 | text-decoration: none; 94 | color: white; 95 | } 96 | 97 | .app-header button { 98 | margin-left: 10px; 99 | color: rgb(255,255,255); 100 | align-self: center; 101 | } 102 | 103 | .app-header button.show-monitor-options { 104 | display: none; 105 | } 106 | 107 | /* Monitor: event list */ 108 | .eventList { 109 | flex-grow: 1; 110 | } 111 | 112 | .eventList.options-sticky { 113 | margin-right: 300px; 114 | } 115 | 116 | .eventList ul { 117 | padding: 0; 118 | margin: 0; 119 | } 120 | .eventList li { 121 | list-style-type: none; 122 | } 123 | 124 | /* Monitor: individual events */ 125 | .event { 126 | padding: 10px 20px; 127 | border-bottom: thin solid #e4e4e4; 128 | overflow: hidden; 129 | } 130 | 131 | .event.expanded { 132 | max-height: auto; 133 | } 134 | 135 | .event p { 136 | margin: 0; 137 | } 138 | 139 | .event header { 140 | display: flex; 141 | margin-bottom: 10px; 142 | } 143 | 144 | .event header .eventType { 145 | flex-grow: 1; 146 | text-transform: uppercase; 147 | font-size: 12px; 148 | font-weight: bold; 149 | color: rgb(111, 111, 111); 150 | } 151 | 152 | .event header .timestamp { 153 | font-size: 13px; 154 | } 155 | 156 | .event .event-body { 157 | display: flex; 158 | font-size: 14px; 159 | margin-bottom: 10px; 160 | } 161 | 162 | .event .details { 163 | flex-grow: 1; 164 | padding-right: 10px; 165 | } 166 | 167 | .event .details .text-description { 168 | border-left: 5px solid rgb(219, 219, 219); 169 | padding-left: 15px; 170 | } 171 | 172 | .event .event-body button { 173 | font-size: 12px; 174 | padding: 0 10px; 175 | padding-right: 24px; 176 | height: 35px; 177 | align-self: center; 178 | position: relative; 179 | } 180 | 181 | .event .event-body button i { 182 | position: absolute; 183 | right: 13px; 184 | top: 52%; 185 | transform: perspective(1px) translate(0, -52%); 186 | font-size: 10px; 187 | transition: right 0.1s; 188 | } 189 | 190 | .event .event-body button:hover i { 191 | right: 11px; 192 | } 193 | 194 | .event .details .summary { 195 | margin-top: 0; 196 | margin-bottom: 10px; 197 | } 198 | 199 | .event .details .summary a { 200 | color: rgb(33, 122, 183); 201 | text-decoration: none; 202 | } 203 | 204 | .event .details .summary a:hover { 205 | border-bottom: thin dotted rgb(33, 122, 183); 206 | } 207 | 208 | .event .details .description { 209 | margin: 0; 210 | font-style: italic; 211 | } 212 | 213 | .event .event-data { 214 | display: flex; 215 | } 216 | 217 | .event .event-data .show-event-data { 218 | cursor: pointer; 219 | font-size: 14px; 220 | user-select: none; 221 | } 222 | 223 | .event .event-data .show-event-data i.icon { 224 | display: inline-block; 225 | margin-right: 5px; 226 | transition: transform 0.2s ease; 227 | } 228 | 229 | .event .event-data .expanded .show-event-data i.icon { 230 | transform: rotate(90deg); 231 | } 232 | 233 | .event .event-data code { 234 | font-size: 12px; 235 | } 236 | 237 | .event .event-data .showJSON { 238 | flex-basis: 200px; 239 | } 240 | 241 | .event .eventJSON, .event .event-metadata { 242 | border: thin solid #ccc; 243 | padding: 5px; 244 | border-radius: 3px; 245 | } 246 | 247 | .event .eventJSON a { 248 | color: inherit; 249 | text-decoration: none; 250 | border-bottom: thin dotted; 251 | } 252 | 253 | .eventList-enter { 254 | opacity: 0; 255 | } 256 | 257 | .eventList-enter-active { 258 | transition: all 0.8s; 259 | } 260 | 261 | .eventList-enter-to { 262 | opacity: 1; 263 | } 264 | 265 | /* Charts */ 266 | .charts { 267 | padding: 14px 16px; 268 | position: relative; 269 | display: flex; 270 | flex-direction: column; 271 | } 272 | 273 | .charts svg { 274 | z-index: 10; 275 | } 276 | 277 | .charts .recentEvents { 278 | flex-grow: 1; 279 | } 280 | 281 | .charts h1 { 282 | margin: 0; 283 | margin-bottom: 20px; 284 | margin-left: 11px; 285 | font-size: 22px; 286 | font-weight: normal; 287 | } 288 | 289 | .charts path { 290 | stroke-width: 1; 291 | } 292 | 293 | .charts .events-area { 294 | height: 100%; 295 | width: 100%; 296 | } 297 | 298 | .charts .events-legend { 299 | position: absolute; 300 | z-index: 50; 301 | top: 20px; 302 | right: 0; 303 | font-size: 13px; 304 | } 305 | 306 | /* Monitor options */ 307 | .monitor { 308 | position: relative; 309 | height: 100%; 310 | } 311 | 312 | .monitor-options { 313 | flex-basis: 250px; 314 | flex-grow: 0; 315 | flex-shrink: 0; 316 | background: #f6f9fc; 317 | padding: 20px 25px; 318 | transition: right 0.2s ease-in-out; 319 | } 320 | 321 | .monitor-options.sticky { 322 | position:fixed; 323 | right: 0; 324 | height: 100%; 325 | top: 0; 326 | width: 250px; 327 | } 328 | 329 | .monitor-options h1 { 330 | text-transform: uppercase; 331 | margin: 0; 332 | font-size: 12px; 333 | color: #32383d; 334 | } 335 | 336 | .monitor-options .filterEvents ul { 337 | list-style-type: none; 338 | margin: 0; 339 | padding: 0; 340 | } 341 | 342 | .monitor-options .filter { 343 | margin: 10px 0; 344 | font-size: 13px; 345 | } 346 | 347 | .monitor-options .filter label { 348 | cursor: pointer; 349 | user-select: none; 350 | -webkit-user-select: none; 351 | -moz-user-select: none; 352 | -ms-user-select: none; 353 | } 354 | 355 | .monitor-options .filter label .count { 356 | font-style: italic; 357 | } 358 | 359 | .monitor-options .filter input[type=checkbox] { 360 | margin: 0; 361 | margin-right: 5px; 362 | display: none; 363 | } 364 | 365 | .monitor-options .filter input[type=checkbox] + span.checkbox:before { 366 | display: inline-block; 367 | font-family: "Ionicons"; 368 | content: "\f372"; /* Unchecked icon */ 369 | letter-spacing: 5px; 370 | font-size: 15px; 371 | 372 | } 373 | 374 | .monitor-options .filter input[type=checkbox]:checked + span.checkbox:before { 375 | content: "\f373"; /* Checked icon */ 376 | } 377 | 378 | .monitor-options .filter .color { 379 | display: inline-block; 380 | width: 12px; 381 | height: 12px; 382 | margin-right: 5px; 383 | } 384 | 385 | /* Responsive details */ 386 | @media (max-width: 650px){ 387 | .app-header { 388 | padding: 0 10px; 389 | } 390 | 391 | .app-header h1 { 392 | font-size: 19px; 393 | } 394 | 395 | .app-header h2 { 396 | font-size: 17px; 397 | } 398 | .app-header button .button-text { 399 | display: none; 400 | } 401 | 402 | .app-header button .icon { 403 | margin-right: 0; 404 | } 405 | } 406 | 407 | @media (max-width: 800px){ 408 | 409 | .app-header button.show-monitor-options { 410 | display: block; 411 | } 412 | 413 | .monitor-options { 414 | position: absolute; 415 | top: 0; 416 | right: -250px; 417 | width: 250px; 418 | height: 100%; 419 | box-sizing: border-box; 420 | box-shadow: -5px 1px 30px 0 rgba(0,0,0,0.1); 421 | } 422 | 423 | .monitor-options.showing { 424 | right: 0; 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /public/vendor/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/stripe-webhook-monitor/cb304fd504cac54dd0f117a61465ad7d9e9e6635/public/vendor/fonts/ionicons.eot -------------------------------------------------------------------------------- /public/vendor/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/stripe-webhook-monitor/cb304fd504cac54dd0f117a61465ad7d9e9e6635/public/vendor/fonts/ionicons.ttf -------------------------------------------------------------------------------- /public/vendor/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/stripe-webhook-monitor/cb304fd504cac54dd0f117a61465ad7d9e9e6635/public/vendor/fonts/ionicons.woff -------------------------------------------------------------------------------- /public/vendor/ionicons.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";/*! 2 | Ionicons, v2.0.1 3 | Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ 4 | https://twitter.com/benjsperry https://twitter.com/ionicframework 5 | MIT License: https://github.com/driftyco/ionicons 6 | 7 | Android-style icons originally built by Google’s 8 | Material Design Icons: https://github.com/google/material-design-icons 9 | used under CC BY http://creativecommons.org/licenses/by/4.0/ 10 | Modified icons to fit ionicon’s grid from original. 11 | */@font-face{font-family:"Ionicons";src:url("./fonts/ionicons.eot?v=2.0.1");src:url("./fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"),url("./fonts/ionicons.ttf?v=2.0.1") format("truetype"),url("./fonts/ionicons.woff?v=2.0.1") format("woff"),url("./fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg");font-weight:normal;font-style:normal}.ion,.ionicons,.ion-alert:before,.ion-alert-circled:before,.ion-android-add:before,.ion-android-add-circle:before,.ion-android-alarm-clock:before,.ion-android-alert:before,.ion-android-apps:before,.ion-android-archive:before,.ion-android-arrow-back:before,.ion-android-arrow-down:before,.ion-android-arrow-dropdown:before,.ion-android-arrow-dropdown-circle:before,.ion-android-arrow-dropleft:before,.ion-android-arrow-dropleft-circle:before,.ion-android-arrow-dropright:before,.ion-android-arrow-dropright-circle:before,.ion-android-arrow-dropup:before,.ion-android-arrow-dropup-circle:before,.ion-android-arrow-forward:before,.ion-android-arrow-up:before,.ion-android-attach:before,.ion-android-bar:before,.ion-android-bicycle:before,.ion-android-boat:before,.ion-android-bookmark:before,.ion-android-bulb:before,.ion-android-bus:before,.ion-android-calendar:before,.ion-android-call:before,.ion-android-camera:before,.ion-android-cancel:before,.ion-android-car:before,.ion-android-cart:before,.ion-android-chat:before,.ion-android-checkbox:before,.ion-android-checkbox-blank:before,.ion-android-checkbox-outline:before,.ion-android-checkbox-outline-blank:before,.ion-android-checkmark-circle:before,.ion-android-clipboard:before,.ion-android-close:before,.ion-android-cloud:before,.ion-android-cloud-circle:before,.ion-android-cloud-done:before,.ion-android-cloud-outline:before,.ion-android-color-palette:before,.ion-android-compass:before,.ion-android-contact:before,.ion-android-contacts:before,.ion-android-contract:before,.ion-android-create:before,.ion-android-delete:before,.ion-android-desktop:before,.ion-android-document:before,.ion-android-done:before,.ion-android-done-all:before,.ion-android-download:before,.ion-android-drafts:before,.ion-android-exit:before,.ion-android-expand:before,.ion-android-favorite:before,.ion-android-favorite-outline:before,.ion-android-film:before,.ion-android-folder:before,.ion-android-folder-open:before,.ion-android-funnel:before,.ion-android-globe:before,.ion-android-hand:before,.ion-android-hangout:before,.ion-android-happy:before,.ion-android-home:before,.ion-android-image:before,.ion-android-laptop:before,.ion-android-list:before,.ion-android-locate:before,.ion-android-lock:before,.ion-android-mail:before,.ion-android-map:before,.ion-android-menu:before,.ion-android-microphone:before,.ion-android-microphone-off:before,.ion-android-more-horizontal:before,.ion-android-more-vertical:before,.ion-android-navigate:before,.ion-android-notifications:before,.ion-android-notifications-none:before,.ion-android-notifications-off:before,.ion-android-open:before,.ion-android-options:before,.ion-android-people:before,.ion-android-person:before,.ion-android-person-add:before,.ion-android-phone-landscape:before,.ion-android-phone-portrait:before,.ion-android-pin:before,.ion-android-plane:before,.ion-android-playstore:before,.ion-android-print:before,.ion-android-radio-button-off:before,.ion-android-radio-button-on:before,.ion-android-refresh:before,.ion-android-remove:before,.ion-android-remove-circle:before,.ion-android-restaurant:before,.ion-android-sad:before,.ion-android-search:before,.ion-android-send:before,.ion-android-settings:before,.ion-android-share:before,.ion-android-share-alt:before,.ion-android-star:before,.ion-android-star-half:before,.ion-android-star-outline:before,.ion-android-stopwatch:before,.ion-android-subway:before,.ion-android-sunny:before,.ion-android-sync:before,.ion-android-textsms:before,.ion-android-time:before,.ion-android-train:before,.ion-android-unlock:before,.ion-android-upload:before,.ion-android-volume-down:before,.ion-android-volume-mute:before,.ion-android-volume-off:before,.ion-android-volume-up:before,.ion-android-walk:before,.ion-android-warning:before,.ion-android-watch:before,.ion-android-wifi:before,.ion-aperture:before,.ion-archive:before,.ion-arrow-down-a:before,.ion-arrow-down-b:before,.ion-arrow-down-c:before,.ion-arrow-expand:before,.ion-arrow-graph-down-left:before,.ion-arrow-graph-down-right:before,.ion-arrow-graph-up-left:before,.ion-arrow-graph-up-right:before,.ion-arrow-left-a:before,.ion-arrow-left-b:before,.ion-arrow-left-c:before,.ion-arrow-move:before,.ion-arrow-resize:before,.ion-arrow-return-left:before,.ion-arrow-return-right:before,.ion-arrow-right-a:before,.ion-arrow-right-b:before,.ion-arrow-right-c:before,.ion-arrow-shrink:before,.ion-arrow-swap:before,.ion-arrow-up-a:before,.ion-arrow-up-b:before,.ion-arrow-up-c:before,.ion-asterisk:before,.ion-at:before,.ion-backspace:before,.ion-backspace-outline:before,.ion-bag:before,.ion-battery-charging:before,.ion-battery-empty:before,.ion-battery-full:before,.ion-battery-half:before,.ion-battery-low:before,.ion-beaker:before,.ion-beer:before,.ion-bluetooth:before,.ion-bonfire:before,.ion-bookmark:before,.ion-bowtie:before,.ion-briefcase:before,.ion-bug:before,.ion-calculator:before,.ion-calendar:before,.ion-camera:before,.ion-card:before,.ion-cash:before,.ion-chatbox:before,.ion-chatbox-working:before,.ion-chatboxes:before,.ion-chatbubble:before,.ion-chatbubble-working:before,.ion-chatbubbles:before,.ion-checkmark:before,.ion-checkmark-circled:before,.ion-checkmark-round:before,.ion-chevron-down:before,.ion-chevron-left:before,.ion-chevron-right:before,.ion-chevron-up:before,.ion-clipboard:before,.ion-clock:before,.ion-close:before,.ion-close-circled:before,.ion-close-round:before,.ion-closed-captioning:before,.ion-cloud:before,.ion-code:before,.ion-code-download:before,.ion-code-working:before,.ion-coffee:before,.ion-compass:before,.ion-compose:before,.ion-connection-bars:before,.ion-contrast:before,.ion-crop:before,.ion-cube:before,.ion-disc:before,.ion-document:before,.ion-document-text:before,.ion-drag:before,.ion-earth:before,.ion-easel:before,.ion-edit:before,.ion-egg:before,.ion-eject:before,.ion-email:before,.ion-email-unread:before,.ion-erlenmeyer-flask:before,.ion-erlenmeyer-flask-bubbles:before,.ion-eye:before,.ion-eye-disabled:before,.ion-female:before,.ion-filing:before,.ion-film-marker:before,.ion-fireball:before,.ion-flag:before,.ion-flame:before,.ion-flash:before,.ion-flash-off:before,.ion-folder:before,.ion-fork:before,.ion-fork-repo:before,.ion-forward:before,.ion-funnel:before,.ion-gear-a:before,.ion-gear-b:before,.ion-grid:before,.ion-hammer:before,.ion-happy:before,.ion-happy-outline:before,.ion-headphone:before,.ion-heart:before,.ion-heart-broken:before,.ion-help:before,.ion-help-buoy:before,.ion-help-circled:before,.ion-home:before,.ion-icecream:before,.ion-image:before,.ion-images:before,.ion-information:before,.ion-information-circled:before,.ion-ionic:before,.ion-ios-alarm:before,.ion-ios-alarm-outline:before,.ion-ios-albums:before,.ion-ios-albums-outline:before,.ion-ios-americanfootball:before,.ion-ios-americanfootball-outline:before,.ion-ios-analytics:before,.ion-ios-analytics-outline:before,.ion-ios-arrow-back:before,.ion-ios-arrow-down:before,.ion-ios-arrow-forward:before,.ion-ios-arrow-left:before,.ion-ios-arrow-right:before,.ion-ios-arrow-thin-down:before,.ion-ios-arrow-thin-left:before,.ion-ios-arrow-thin-right:before,.ion-ios-arrow-thin-up:before,.ion-ios-arrow-up:before,.ion-ios-at:before,.ion-ios-at-outline:before,.ion-ios-barcode:before,.ion-ios-barcode-outline:before,.ion-ios-baseball:before,.ion-ios-baseball-outline:before,.ion-ios-basketball:before,.ion-ios-basketball-outline:before,.ion-ios-bell:before,.ion-ios-bell-outline:before,.ion-ios-body:before,.ion-ios-body-outline:before,.ion-ios-bolt:before,.ion-ios-bolt-outline:before,.ion-ios-book:before,.ion-ios-book-outline:before,.ion-ios-bookmarks:before,.ion-ios-bookmarks-outline:before,.ion-ios-box:before,.ion-ios-box-outline:before,.ion-ios-briefcase:before,.ion-ios-briefcase-outline:before,.ion-ios-browsers:before,.ion-ios-browsers-outline:before,.ion-ios-calculator:before,.ion-ios-calculator-outline:before,.ion-ios-calendar:before,.ion-ios-calendar-outline:before,.ion-ios-camera:before,.ion-ios-camera-outline:before,.ion-ios-cart:before,.ion-ios-cart-outline:before,.ion-ios-chatboxes:before,.ion-ios-chatboxes-outline:before,.ion-ios-chatbubble:before,.ion-ios-chatbubble-outline:before,.ion-ios-checkmark:before,.ion-ios-checkmark-empty:before,.ion-ios-checkmark-outline:before,.ion-ios-circle-filled:before,.ion-ios-circle-outline:before,.ion-ios-clock:before,.ion-ios-clock-outline:before,.ion-ios-close:before,.ion-ios-close-empty:before,.ion-ios-close-outline:before,.ion-ios-cloud:before,.ion-ios-cloud-download:before,.ion-ios-cloud-download-outline:before,.ion-ios-cloud-outline:before,.ion-ios-cloud-upload:before,.ion-ios-cloud-upload-outline:before,.ion-ios-cloudy:before,.ion-ios-cloudy-night:before,.ion-ios-cloudy-night-outline:before,.ion-ios-cloudy-outline:before,.ion-ios-cog:before,.ion-ios-cog-outline:before,.ion-ios-color-filter:before,.ion-ios-color-filter-outline:before,.ion-ios-color-wand:before,.ion-ios-color-wand-outline:before,.ion-ios-compose:before,.ion-ios-compose-outline:before,.ion-ios-contact:before,.ion-ios-contact-outline:before,.ion-ios-copy:before,.ion-ios-copy-outline:before,.ion-ios-crop:before,.ion-ios-crop-strong:before,.ion-ios-download:before,.ion-ios-download-outline:before,.ion-ios-drag:before,.ion-ios-email:before,.ion-ios-email-outline:before,.ion-ios-eye:before,.ion-ios-eye-outline:before,.ion-ios-fastforward:before,.ion-ios-fastforward-outline:before,.ion-ios-filing:before,.ion-ios-filing-outline:before,.ion-ios-film:before,.ion-ios-film-outline:before,.ion-ios-flag:before,.ion-ios-flag-outline:before,.ion-ios-flame:before,.ion-ios-flame-outline:before,.ion-ios-flask:before,.ion-ios-flask-outline:before,.ion-ios-flower:before,.ion-ios-flower-outline:before,.ion-ios-folder:before,.ion-ios-folder-outline:before,.ion-ios-football:before,.ion-ios-football-outline:before,.ion-ios-game-controller-a:before,.ion-ios-game-controller-a-outline:before,.ion-ios-game-controller-b:before,.ion-ios-game-controller-b-outline:before,.ion-ios-gear:before,.ion-ios-gear-outline:before,.ion-ios-glasses:before,.ion-ios-glasses-outline:before,.ion-ios-grid-view:before,.ion-ios-grid-view-outline:before,.ion-ios-heart:before,.ion-ios-heart-outline:before,.ion-ios-help:before,.ion-ios-help-empty:before,.ion-ios-help-outline:before,.ion-ios-home:before,.ion-ios-home-outline:before,.ion-ios-infinite:before,.ion-ios-infinite-outline:before,.ion-ios-information:before,.ion-ios-information-empty:before,.ion-ios-information-outline:before,.ion-ios-ionic-outline:before,.ion-ios-keypad:before,.ion-ios-keypad-outline:before,.ion-ios-lightbulb:before,.ion-ios-lightbulb-outline:before,.ion-ios-list:before,.ion-ios-list-outline:before,.ion-ios-location:before,.ion-ios-location-outline:before,.ion-ios-locked:before,.ion-ios-locked-outline:before,.ion-ios-loop:before,.ion-ios-loop-strong:before,.ion-ios-medical:before,.ion-ios-medical-outline:before,.ion-ios-medkit:before,.ion-ios-medkit-outline:before,.ion-ios-mic:before,.ion-ios-mic-off:before,.ion-ios-mic-outline:before,.ion-ios-minus:before,.ion-ios-minus-empty:before,.ion-ios-minus-outline:before,.ion-ios-monitor:before,.ion-ios-monitor-outline:before,.ion-ios-moon:before,.ion-ios-moon-outline:before,.ion-ios-more:before,.ion-ios-more-outline:before,.ion-ios-musical-note:before,.ion-ios-musical-notes:before,.ion-ios-navigate:before,.ion-ios-navigate-outline:before,.ion-ios-nutrition:before,.ion-ios-nutrition-outline:before,.ion-ios-paper:before,.ion-ios-paper-outline:before,.ion-ios-paperplane:before,.ion-ios-paperplane-outline:before,.ion-ios-partlysunny:before,.ion-ios-partlysunny-outline:before,.ion-ios-pause:before,.ion-ios-pause-outline:before,.ion-ios-paw:before,.ion-ios-paw-outline:before,.ion-ios-people:before,.ion-ios-people-outline:before,.ion-ios-person:before,.ion-ios-person-outline:before,.ion-ios-personadd:before,.ion-ios-personadd-outline:before,.ion-ios-photos:before,.ion-ios-photos-outline:before,.ion-ios-pie:before,.ion-ios-pie-outline:before,.ion-ios-pint:before,.ion-ios-pint-outline:before,.ion-ios-play:before,.ion-ios-play-outline:before,.ion-ios-plus:before,.ion-ios-plus-empty:before,.ion-ios-plus-outline:before,.ion-ios-pricetag:before,.ion-ios-pricetag-outline:before,.ion-ios-pricetags:before,.ion-ios-pricetags-outline:before,.ion-ios-printer:before,.ion-ios-printer-outline:before,.ion-ios-pulse:before,.ion-ios-pulse-strong:before,.ion-ios-rainy:before,.ion-ios-rainy-outline:before,.ion-ios-recording:before,.ion-ios-recording-outline:before,.ion-ios-redo:before,.ion-ios-redo-outline:before,.ion-ios-refresh:before,.ion-ios-refresh-empty:before,.ion-ios-refresh-outline:before,.ion-ios-reload:before,.ion-ios-reverse-camera:before,.ion-ios-reverse-camera-outline:before,.ion-ios-rewind:before,.ion-ios-rewind-outline:before,.ion-ios-rose:before,.ion-ios-rose-outline:before,.ion-ios-search:before,.ion-ios-search-strong:before,.ion-ios-settings:before,.ion-ios-settings-strong:before,.ion-ios-shuffle:before,.ion-ios-shuffle-strong:before,.ion-ios-skipbackward:before,.ion-ios-skipbackward-outline:before,.ion-ios-skipforward:before,.ion-ios-skipforward-outline:before,.ion-ios-snowy:before,.ion-ios-speedometer:before,.ion-ios-speedometer-outline:before,.ion-ios-star:before,.ion-ios-star-half:before,.ion-ios-star-outline:before,.ion-ios-stopwatch:before,.ion-ios-stopwatch-outline:before,.ion-ios-sunny:before,.ion-ios-sunny-outline:before,.ion-ios-telephone:before,.ion-ios-telephone-outline:before,.ion-ios-tennisball:before,.ion-ios-tennisball-outline:before,.ion-ios-thunderstorm:before,.ion-ios-thunderstorm-outline:before,.ion-ios-time:before,.ion-ios-time-outline:before,.ion-ios-timer:before,.ion-ios-timer-outline:before,.ion-ios-toggle:before,.ion-ios-toggle-outline:before,.ion-ios-trash:before,.ion-ios-trash-outline:before,.ion-ios-undo:before,.ion-ios-undo-outline:before,.ion-ios-unlocked:before,.ion-ios-unlocked-outline:before,.ion-ios-upload:before,.ion-ios-upload-outline:before,.ion-ios-videocam:before,.ion-ios-videocam-outline:before,.ion-ios-volume-high:before,.ion-ios-volume-low:before,.ion-ios-wineglass:before,.ion-ios-wineglass-outline:before,.ion-ios-world:before,.ion-ios-world-outline:before,.ion-ipad:before,.ion-iphone:before,.ion-ipod:before,.ion-jet:before,.ion-key:before,.ion-knife:before,.ion-laptop:before,.ion-leaf:before,.ion-levels:before,.ion-lightbulb:before,.ion-link:before,.ion-load-a:before,.ion-load-b:before,.ion-load-c:before,.ion-load-d:before,.ion-location:before,.ion-lock-combination:before,.ion-locked:before,.ion-log-in:before,.ion-log-out:before,.ion-loop:before,.ion-magnet:before,.ion-male:before,.ion-man:before,.ion-map:before,.ion-medkit:before,.ion-merge:before,.ion-mic-a:before,.ion-mic-b:before,.ion-mic-c:before,.ion-minus:before,.ion-minus-circled:before,.ion-minus-round:before,.ion-model-s:before,.ion-monitor:before,.ion-more:before,.ion-mouse:before,.ion-music-note:before,.ion-navicon:before,.ion-navicon-round:before,.ion-navigate:before,.ion-network:before,.ion-no-smoking:before,.ion-nuclear:before,.ion-outlet:before,.ion-paintbrush:before,.ion-paintbucket:before,.ion-paper-airplane:before,.ion-paperclip:before,.ion-pause:before,.ion-person:before,.ion-person-add:before,.ion-person-stalker:before,.ion-pie-graph:before,.ion-pin:before,.ion-pinpoint:before,.ion-pizza:before,.ion-plane:before,.ion-planet:before,.ion-play:before,.ion-playstation:before,.ion-plus:before,.ion-plus-circled:before,.ion-plus-round:before,.ion-podium:before,.ion-pound:before,.ion-power:before,.ion-pricetag:before,.ion-pricetags:before,.ion-printer:before,.ion-pull-request:before,.ion-qr-scanner:before,.ion-quote:before,.ion-radio-waves:before,.ion-record:before,.ion-refresh:before,.ion-reply:before,.ion-reply-all:before,.ion-ribbon-a:before,.ion-ribbon-b:before,.ion-sad:before,.ion-sad-outline:before,.ion-scissors:before,.ion-search:before,.ion-settings:before,.ion-share:before,.ion-shuffle:before,.ion-skip-backward:before,.ion-skip-forward:before,.ion-social-android:before,.ion-social-android-outline:before,.ion-social-angular:before,.ion-social-angular-outline:before,.ion-social-apple:before,.ion-social-apple-outline:before,.ion-social-bitcoin:before,.ion-social-bitcoin-outline:before,.ion-social-buffer:before,.ion-social-buffer-outline:before,.ion-social-chrome:before,.ion-social-chrome-outline:before,.ion-social-codepen:before,.ion-social-codepen-outline:before,.ion-social-css3:before,.ion-social-css3-outline:before,.ion-social-designernews:before,.ion-social-designernews-outline:before,.ion-social-dribbble:before,.ion-social-dribbble-outline:before,.ion-social-dropbox:before,.ion-social-dropbox-outline:before,.ion-social-euro:before,.ion-social-euro-outline:before,.ion-social-facebook:before,.ion-social-facebook-outline:before,.ion-social-foursquare:before,.ion-social-foursquare-outline:before,.ion-social-freebsd-devil:before,.ion-social-github:before,.ion-social-github-outline:before,.ion-social-google:before,.ion-social-google-outline:before,.ion-social-googleplus:before,.ion-social-googleplus-outline:before,.ion-social-hackernews:before,.ion-social-hackernews-outline:before,.ion-social-html5:before,.ion-social-html5-outline:before,.ion-social-instagram:before,.ion-social-instagram-outline:before,.ion-social-javascript:before,.ion-social-javascript-outline:before,.ion-social-linkedin:before,.ion-social-linkedin-outline:before,.ion-social-markdown:before,.ion-social-nodejs:before,.ion-social-octocat:before,.ion-social-pinterest:before,.ion-social-pinterest-outline:before,.ion-social-python:before,.ion-social-reddit:before,.ion-social-reddit-outline:before,.ion-social-rss:before,.ion-social-rss-outline:before,.ion-social-sass:before,.ion-social-skype:before,.ion-social-skype-outline:before,.ion-social-snapchat:before,.ion-social-snapchat-outline:before,.ion-social-tumblr:before,.ion-social-tumblr-outline:before,.ion-social-tux:before,.ion-social-twitch:before,.ion-social-twitch-outline:before,.ion-social-twitter:before,.ion-social-twitter-outline:before,.ion-social-usd:before,.ion-social-usd-outline:before,.ion-social-vimeo:before,.ion-social-vimeo-outline:before,.ion-social-whatsapp:before,.ion-social-whatsapp-outline:before,.ion-social-windows:before,.ion-social-windows-outline:before,.ion-social-wordpress:before,.ion-social-wordpress-outline:before,.ion-social-yahoo:before,.ion-social-yahoo-outline:before,.ion-social-yen:before,.ion-social-yen-outline:before,.ion-social-youtube:before,.ion-social-youtube-outline:before,.ion-soup-can:before,.ion-soup-can-outline:before,.ion-speakerphone:before,.ion-speedometer:before,.ion-spoon:before,.ion-star:before,.ion-stats-bars:before,.ion-steam:before,.ion-stop:before,.ion-thermometer:before,.ion-thumbsdown:before,.ion-thumbsup:before,.ion-toggle:before,.ion-toggle-filled:before,.ion-transgender:before,.ion-trash-a:before,.ion-trash-b:before,.ion-trophy:before,.ion-tshirt:before,.ion-tshirt-outline:before,.ion-umbrella:before,.ion-university:before,.ion-unlocked:before,.ion-upload:before,.ion-usb:before,.ion-videocamera:before,.ion-volume-high:before,.ion-volume-low:before,.ion-volume-medium:before,.ion-volume-mute:before,.ion-wand:before,.ion-waterdrop:before,.ion-wifi:before,.ion-wineglass:before,.ion-woman:before,.ion-wrench:before,.ion-xbox:before{display:inline-block;font-family:"Ionicons";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;text-rendering:auto;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ion-alert:before{content:"\f101"}.ion-alert-circled:before{content:"\f100"}.ion-android-add:before{content:"\f2c7"}.ion-android-add-circle:before{content:"\f359"}.ion-android-alarm-clock:before{content:"\f35a"}.ion-android-alert:before{content:"\f35b"}.ion-android-apps:before{content:"\f35c"}.ion-android-archive:before{content:"\f2c9"}.ion-android-arrow-back:before{content:"\f2ca"}.ion-android-arrow-down:before{content:"\f35d"}.ion-android-arrow-dropdown:before{content:"\f35f"}.ion-android-arrow-dropdown-circle:before{content:"\f35e"}.ion-android-arrow-dropleft:before{content:"\f361"}.ion-android-arrow-dropleft-circle:before{content:"\f360"}.ion-android-arrow-dropright:before{content:"\f363"}.ion-android-arrow-dropright-circle:before{content:"\f362"}.ion-android-arrow-dropup:before{content:"\f365"}.ion-android-arrow-dropup-circle:before{content:"\f364"}.ion-android-arrow-forward:before{content:"\f30f"}.ion-android-arrow-up:before{content:"\f366"}.ion-android-attach:before{content:"\f367"}.ion-android-bar:before{content:"\f368"}.ion-android-bicycle:before{content:"\f369"}.ion-android-boat:before{content:"\f36a"}.ion-android-bookmark:before{content:"\f36b"}.ion-android-bulb:before{content:"\f36c"}.ion-android-bus:before{content:"\f36d"}.ion-android-calendar:before{content:"\f2d1"}.ion-android-call:before{content:"\f2d2"}.ion-android-camera:before{content:"\f2d3"}.ion-android-cancel:before{content:"\f36e"}.ion-android-car:before{content:"\f36f"}.ion-android-cart:before{content:"\f370"}.ion-android-chat:before{content:"\f2d4"}.ion-android-checkbox:before{content:"\f374"}.ion-android-checkbox-blank:before{content:"\f371"}.ion-android-checkbox-outline:before{content:"\f373"}.ion-android-checkbox-outline-blank:before{content:"\f372"}.ion-android-checkmark-circle:before{content:"\f375"}.ion-android-clipboard:before{content:"\f376"}.ion-android-close:before{content:"\f2d7"}.ion-android-cloud:before{content:"\f37a"}.ion-android-cloud-circle:before{content:"\f377"}.ion-android-cloud-done:before{content:"\f378"}.ion-android-cloud-outline:before{content:"\f379"}.ion-android-color-palette:before{content:"\f37b"}.ion-android-compass:before{content:"\f37c"}.ion-android-contact:before{content:"\f2d8"}.ion-android-contacts:before{content:"\f2d9"}.ion-android-contract:before{content:"\f37d"}.ion-android-create:before{content:"\f37e"}.ion-android-delete:before{content:"\f37f"}.ion-android-desktop:before{content:"\f380"}.ion-android-document:before{content:"\f381"}.ion-android-done:before{content:"\f383"}.ion-android-done-all:before{content:"\f382"}.ion-android-download:before{content:"\f2dd"}.ion-android-drafts:before{content:"\f384"}.ion-android-exit:before{content:"\f385"}.ion-android-expand:before{content:"\f386"}.ion-android-favorite:before{content:"\f388"}.ion-android-favorite-outline:before{content:"\f387"}.ion-android-film:before{content:"\f389"}.ion-android-folder:before{content:"\f2e0"}.ion-android-folder-open:before{content:"\f38a"}.ion-android-funnel:before{content:"\f38b"}.ion-android-globe:before{content:"\f38c"}.ion-android-hand:before{content:"\f2e3"}.ion-android-hangout:before{content:"\f38d"}.ion-android-happy:before{content:"\f38e"}.ion-android-home:before{content:"\f38f"}.ion-android-image:before{content:"\f2e4"}.ion-android-laptop:before{content:"\f390"}.ion-android-list:before{content:"\f391"}.ion-android-locate:before{content:"\f2e9"}.ion-android-lock:before{content:"\f392"}.ion-android-mail:before{content:"\f2eb"}.ion-android-map:before{content:"\f393"}.ion-android-menu:before{content:"\f394"}.ion-android-microphone:before{content:"\f2ec"}.ion-android-microphone-off:before{content:"\f395"}.ion-android-more-horizontal:before{content:"\f396"}.ion-android-more-vertical:before{content:"\f397"}.ion-android-navigate:before{content:"\f398"}.ion-android-notifications:before{content:"\f39b"}.ion-android-notifications-none:before{content:"\f399"}.ion-android-notifications-off:before{content:"\f39a"}.ion-android-open:before{content:"\f39c"}.ion-android-options:before{content:"\f39d"}.ion-android-people:before{content:"\f39e"}.ion-android-person:before{content:"\f3a0"}.ion-android-person-add:before{content:"\f39f"}.ion-android-phone-landscape:before{content:"\f3a1"}.ion-android-phone-portrait:before{content:"\f3a2"}.ion-android-pin:before{content:"\f3a3"}.ion-android-plane:before{content:"\f3a4"}.ion-android-playstore:before{content:"\f2f0"}.ion-android-print:before{content:"\f3a5"}.ion-android-radio-button-off:before{content:"\f3a6"}.ion-android-radio-button-on:before{content:"\f3a7"}.ion-android-refresh:before{content:"\f3a8"}.ion-android-remove:before{content:"\f2f4"}.ion-android-remove-circle:before{content:"\f3a9"}.ion-android-restaurant:before{content:"\f3aa"}.ion-android-sad:before{content:"\f3ab"}.ion-android-search:before{content:"\f2f5"}.ion-android-send:before{content:"\f2f6"}.ion-android-settings:before{content:"\f2f7"}.ion-android-share:before{content:"\f2f8"}.ion-android-share-alt:before{content:"\f3ac"}.ion-android-star:before{content:"\f2fc"}.ion-android-star-half:before{content:"\f3ad"}.ion-android-star-outline:before{content:"\f3ae"}.ion-android-stopwatch:before{content:"\f2fd"}.ion-android-subway:before{content:"\f3af"}.ion-android-sunny:before{content:"\f3b0"}.ion-android-sync:before{content:"\f3b1"}.ion-android-textsms:before{content:"\f3b2"}.ion-android-time:before{content:"\f3b3"}.ion-android-train:before{content:"\f3b4"}.ion-android-unlock:before{content:"\f3b5"}.ion-android-upload:before{content:"\f3b6"}.ion-android-volume-down:before{content:"\f3b7"}.ion-android-volume-mute:before{content:"\f3b8"}.ion-android-volume-off:before{content:"\f3b9"}.ion-android-volume-up:before{content:"\f3ba"}.ion-android-walk:before{content:"\f3bb"}.ion-android-warning:before{content:"\f3bc"}.ion-android-watch:before{content:"\f3bd"}.ion-android-wifi:before{content:"\f305"}.ion-aperture:before{content:"\f313"}.ion-archive:before{content:"\f102"}.ion-arrow-down-a:before{content:"\f103"}.ion-arrow-down-b:before{content:"\f104"}.ion-arrow-down-c:before{content:"\f105"}.ion-arrow-expand:before{content:"\f25e"}.ion-arrow-graph-down-left:before{content:"\f25f"}.ion-arrow-graph-down-right:before{content:"\f260"}.ion-arrow-graph-up-left:before{content:"\f261"}.ion-arrow-graph-up-right:before{content:"\f262"}.ion-arrow-left-a:before{content:"\f106"}.ion-arrow-left-b:before{content:"\f107"}.ion-arrow-left-c:before{content:"\f108"}.ion-arrow-move:before{content:"\f263"}.ion-arrow-resize:before{content:"\f264"}.ion-arrow-return-left:before{content:"\f265"}.ion-arrow-return-right:before{content:"\f266"}.ion-arrow-right-a:before{content:"\f109"}.ion-arrow-right-b:before{content:"\f10a"}.ion-arrow-right-c:before{content:"\f10b"}.ion-arrow-shrink:before{content:"\f267"}.ion-arrow-swap:before{content:"\f268"}.ion-arrow-up-a:before{content:"\f10c"}.ion-arrow-up-b:before{content:"\f10d"}.ion-arrow-up-c:before{content:"\f10e"}.ion-asterisk:before{content:"\f314"}.ion-at:before{content:"\f10f"}.ion-backspace:before{content:"\f3bf"}.ion-backspace-outline:before{content:"\f3be"}.ion-bag:before{content:"\f110"}.ion-battery-charging:before{content:"\f111"}.ion-battery-empty:before{content:"\f112"}.ion-battery-full:before{content:"\f113"}.ion-battery-half:before{content:"\f114"}.ion-battery-low:before{content:"\f115"}.ion-beaker:before{content:"\f269"}.ion-beer:before{content:"\f26a"}.ion-bluetooth:before{content:"\f116"}.ion-bonfire:before{content:"\f315"}.ion-bookmark:before{content:"\f26b"}.ion-bowtie:before{content:"\f3c0"}.ion-briefcase:before{content:"\f26c"}.ion-bug:before{content:"\f2be"}.ion-calculator:before{content:"\f26d"}.ion-calendar:before{content:"\f117"}.ion-camera:before{content:"\f118"}.ion-card:before{content:"\f119"}.ion-cash:before{content:"\f316"}.ion-chatbox:before{content:"\f11b"}.ion-chatbox-working:before{content:"\f11a"}.ion-chatboxes:before{content:"\f11c"}.ion-chatbubble:before{content:"\f11e"}.ion-chatbubble-working:before{content:"\f11d"}.ion-chatbubbles:before{content:"\f11f"}.ion-checkmark:before{content:"\f122"}.ion-checkmark-circled:before{content:"\f120"}.ion-checkmark-round:before{content:"\f121"}.ion-chevron-down:before{content:"\f123"}.ion-chevron-left:before{content:"\f124"}.ion-chevron-right:before{content:"\f125"}.ion-chevron-up:before{content:"\f126"}.ion-clipboard:before{content:"\f127"}.ion-clock:before{content:"\f26e"}.ion-close:before{content:"\f12a"}.ion-close-circled:before{content:"\f128"}.ion-close-round:before{content:"\f129"}.ion-closed-captioning:before{content:"\f317"}.ion-cloud:before{content:"\f12b"}.ion-code:before{content:"\f271"}.ion-code-download:before{content:"\f26f"}.ion-code-working:before{content:"\f270"}.ion-coffee:before{content:"\f272"}.ion-compass:before{content:"\f273"}.ion-compose:before{content:"\f12c"}.ion-connection-bars:before{content:"\f274"}.ion-contrast:before{content:"\f275"}.ion-crop:before{content:"\f3c1"}.ion-cube:before{content:"\f318"}.ion-disc:before{content:"\f12d"}.ion-document:before{content:"\f12f"}.ion-document-text:before{content:"\f12e"}.ion-drag:before{content:"\f130"}.ion-earth:before{content:"\f276"}.ion-easel:before{content:"\f3c2"}.ion-edit:before{content:"\f2bf"}.ion-egg:before{content:"\f277"}.ion-eject:before{content:"\f131"}.ion-email:before{content:"\f132"}.ion-email-unread:before{content:"\f3c3"}.ion-erlenmeyer-flask:before{content:"\f3c5"}.ion-erlenmeyer-flask-bubbles:before{content:"\f3c4"}.ion-eye:before{content:"\f133"}.ion-eye-disabled:before{content:"\f306"}.ion-female:before{content:"\f278"}.ion-filing:before{content:"\f134"}.ion-film-marker:before{content:"\f135"}.ion-fireball:before{content:"\f319"}.ion-flag:before{content:"\f279"}.ion-flame:before{content:"\f31a"}.ion-flash:before{content:"\f137"}.ion-flash-off:before{content:"\f136"}.ion-folder:before{content:"\f139"}.ion-fork:before{content:"\f27a"}.ion-fork-repo:before{content:"\f2c0"}.ion-forward:before{content:"\f13a"}.ion-funnel:before{content:"\f31b"}.ion-gear-a:before{content:"\f13d"}.ion-gear-b:before{content:"\f13e"}.ion-grid:before{content:"\f13f"}.ion-hammer:before{content:"\f27b"}.ion-happy:before{content:"\f31c"}.ion-happy-outline:before{content:"\f3c6"}.ion-headphone:before{content:"\f140"}.ion-heart:before{content:"\f141"}.ion-heart-broken:before{content:"\f31d"}.ion-help:before{content:"\f143"}.ion-help-buoy:before{content:"\f27c"}.ion-help-circled:before{content:"\f142"}.ion-home:before{content:"\f144"}.ion-icecream:before{content:"\f27d"}.ion-image:before{content:"\f147"}.ion-images:before{content:"\f148"}.ion-information:before{content:"\f14a"}.ion-information-circled:before{content:"\f149"}.ion-ionic:before{content:"\f14b"}.ion-ios-alarm:before{content:"\f3c8"}.ion-ios-alarm-outline:before{content:"\f3c7"}.ion-ios-albums:before{content:"\f3ca"}.ion-ios-albums-outline:before{content:"\f3c9"}.ion-ios-americanfootball:before{content:"\f3cc"}.ion-ios-americanfootball-outline:before{content:"\f3cb"}.ion-ios-analytics:before{content:"\f3ce"}.ion-ios-analytics-outline:before{content:"\f3cd"}.ion-ios-arrow-back:before{content:"\f3cf"}.ion-ios-arrow-down:before{content:"\f3d0"}.ion-ios-arrow-forward:before{content:"\f3d1"}.ion-ios-arrow-left:before{content:"\f3d2"}.ion-ios-arrow-right:before{content:"\f3d3"}.ion-ios-arrow-thin-down:before{content:"\f3d4"}.ion-ios-arrow-thin-left:before{content:"\f3d5"}.ion-ios-arrow-thin-right:before{content:"\f3d6"}.ion-ios-arrow-thin-up:before{content:"\f3d7"}.ion-ios-arrow-up:before{content:"\f3d8"}.ion-ios-at:before{content:"\f3da"}.ion-ios-at-outline:before{content:"\f3d9"}.ion-ios-barcode:before{content:"\f3dc"}.ion-ios-barcode-outline:before{content:"\f3db"}.ion-ios-baseball:before{content:"\f3de"}.ion-ios-baseball-outline:before{content:"\f3dd"}.ion-ios-basketball:before{content:"\f3e0"}.ion-ios-basketball-outline:before{content:"\f3df"}.ion-ios-bell:before{content:"\f3e2"}.ion-ios-bell-outline:before{content:"\f3e1"}.ion-ios-body:before{content:"\f3e4"}.ion-ios-body-outline:before{content:"\f3e3"}.ion-ios-bolt:before{content:"\f3e6"}.ion-ios-bolt-outline:before{content:"\f3e5"}.ion-ios-book:before{content:"\f3e8"}.ion-ios-book-outline:before{content:"\f3e7"}.ion-ios-bookmarks:before{content:"\f3ea"}.ion-ios-bookmarks-outline:before{content:"\f3e9"}.ion-ios-box:before{content:"\f3ec"}.ion-ios-box-outline:before{content:"\f3eb"}.ion-ios-briefcase:before{content:"\f3ee"}.ion-ios-briefcase-outline:before{content:"\f3ed"}.ion-ios-browsers:before{content:"\f3f0"}.ion-ios-browsers-outline:before{content:"\f3ef"}.ion-ios-calculator:before{content:"\f3f2"}.ion-ios-calculator-outline:before{content:"\f3f1"}.ion-ios-calendar:before{content:"\f3f4"}.ion-ios-calendar-outline:before{content:"\f3f3"}.ion-ios-camera:before{content:"\f3f6"}.ion-ios-camera-outline:before{content:"\f3f5"}.ion-ios-cart:before{content:"\f3f8"}.ion-ios-cart-outline:before{content:"\f3f7"}.ion-ios-chatboxes:before{content:"\f3fa"}.ion-ios-chatboxes-outline:before{content:"\f3f9"}.ion-ios-chatbubble:before{content:"\f3fc"}.ion-ios-chatbubble-outline:before{content:"\f3fb"}.ion-ios-checkmark:before{content:"\f3ff"}.ion-ios-checkmark-empty:before{content:"\f3fd"}.ion-ios-checkmark-outline:before{content:"\f3fe"}.ion-ios-circle-filled:before{content:"\f400"}.ion-ios-circle-outline:before{content:"\f401"}.ion-ios-clock:before{content:"\f403"}.ion-ios-clock-outline:before{content:"\f402"}.ion-ios-close:before{content:"\f406"}.ion-ios-close-empty:before{content:"\f404"}.ion-ios-close-outline:before{content:"\f405"}.ion-ios-cloud:before{content:"\f40c"}.ion-ios-cloud-download:before{content:"\f408"}.ion-ios-cloud-download-outline:before{content:"\f407"}.ion-ios-cloud-outline:before{content:"\f409"}.ion-ios-cloud-upload:before{content:"\f40b"}.ion-ios-cloud-upload-outline:before{content:"\f40a"}.ion-ios-cloudy:before{content:"\f410"}.ion-ios-cloudy-night:before{content:"\f40e"}.ion-ios-cloudy-night-outline:before{content:"\f40d"}.ion-ios-cloudy-outline:before{content:"\f40f"}.ion-ios-cog:before{content:"\f412"}.ion-ios-cog-outline:before{content:"\f411"}.ion-ios-color-filter:before{content:"\f414"}.ion-ios-color-filter-outline:before{content:"\f413"}.ion-ios-color-wand:before{content:"\f416"}.ion-ios-color-wand-outline:before{content:"\f415"}.ion-ios-compose:before{content:"\f418"}.ion-ios-compose-outline:before{content:"\f417"}.ion-ios-contact:before{content:"\f41a"}.ion-ios-contact-outline:before{content:"\f419"}.ion-ios-copy:before{content:"\f41c"}.ion-ios-copy-outline:before{content:"\f41b"}.ion-ios-crop:before{content:"\f41e"}.ion-ios-crop-strong:before{content:"\f41d"}.ion-ios-download:before{content:"\f420"}.ion-ios-download-outline:before{content:"\f41f"}.ion-ios-drag:before{content:"\f421"}.ion-ios-email:before{content:"\f423"}.ion-ios-email-outline:before{content:"\f422"}.ion-ios-eye:before{content:"\f425"}.ion-ios-eye-outline:before{content:"\f424"}.ion-ios-fastforward:before{content:"\f427"}.ion-ios-fastforward-outline:before{content:"\f426"}.ion-ios-filing:before{content:"\f429"}.ion-ios-filing-outline:before{content:"\f428"}.ion-ios-film:before{content:"\f42b"}.ion-ios-film-outline:before{content:"\f42a"}.ion-ios-flag:before{content:"\f42d"}.ion-ios-flag-outline:before{content:"\f42c"}.ion-ios-flame:before{content:"\f42f"}.ion-ios-flame-outline:before{content:"\f42e"}.ion-ios-flask:before{content:"\f431"}.ion-ios-flask-outline:before{content:"\f430"}.ion-ios-flower:before{content:"\f433"}.ion-ios-flower-outline:before{content:"\f432"}.ion-ios-folder:before{content:"\f435"}.ion-ios-folder-outline:before{content:"\f434"}.ion-ios-football:before{content:"\f437"}.ion-ios-football-outline:before{content:"\f436"}.ion-ios-game-controller-a:before{content:"\f439"}.ion-ios-game-controller-a-outline:before{content:"\f438"}.ion-ios-game-controller-b:before{content:"\f43b"}.ion-ios-game-controller-b-outline:before{content:"\f43a"}.ion-ios-gear:before{content:"\f43d"}.ion-ios-gear-outline:before{content:"\f43c"}.ion-ios-glasses:before{content:"\f43f"}.ion-ios-glasses-outline:before{content:"\f43e"}.ion-ios-grid-view:before{content:"\f441"}.ion-ios-grid-view-outline:before{content:"\f440"}.ion-ios-heart:before{content:"\f443"}.ion-ios-heart-outline:before{content:"\f442"}.ion-ios-help:before{content:"\f446"}.ion-ios-help-empty:before{content:"\f444"}.ion-ios-help-outline:before{content:"\f445"}.ion-ios-home:before{content:"\f448"}.ion-ios-home-outline:before{content:"\f447"}.ion-ios-infinite:before{content:"\f44a"}.ion-ios-infinite-outline:before{content:"\f449"}.ion-ios-information:before{content:"\f44d"}.ion-ios-information-empty:before{content:"\f44b"}.ion-ios-information-outline:before{content:"\f44c"}.ion-ios-ionic-outline:before{content:"\f44e"}.ion-ios-keypad:before{content:"\f450"}.ion-ios-keypad-outline:before{content:"\f44f"}.ion-ios-lightbulb:before{content:"\f452"}.ion-ios-lightbulb-outline:before{content:"\f451"}.ion-ios-list:before{content:"\f454"}.ion-ios-list-outline:before{content:"\f453"}.ion-ios-location:before{content:"\f456"}.ion-ios-location-outline:before{content:"\f455"}.ion-ios-locked:before{content:"\f458"}.ion-ios-locked-outline:before{content:"\f457"}.ion-ios-loop:before{content:"\f45a"}.ion-ios-loop-strong:before{content:"\f459"}.ion-ios-medical:before{content:"\f45c"}.ion-ios-medical-outline:before{content:"\f45b"}.ion-ios-medkit:before{content:"\f45e"}.ion-ios-medkit-outline:before{content:"\f45d"}.ion-ios-mic:before{content:"\f461"}.ion-ios-mic-off:before{content:"\f45f"}.ion-ios-mic-outline:before{content:"\f460"}.ion-ios-minus:before{content:"\f464"}.ion-ios-minus-empty:before{content:"\f462"}.ion-ios-minus-outline:before{content:"\f463"}.ion-ios-monitor:before{content:"\f466"}.ion-ios-monitor-outline:before{content:"\f465"}.ion-ios-moon:before{content:"\f468"}.ion-ios-moon-outline:before{content:"\f467"}.ion-ios-more:before{content:"\f46a"}.ion-ios-more-outline:before{content:"\f469"}.ion-ios-musical-note:before{content:"\f46b"}.ion-ios-musical-notes:before{content:"\f46c"}.ion-ios-navigate:before{content:"\f46e"}.ion-ios-navigate-outline:before{content:"\f46d"}.ion-ios-nutrition:before{content:"\f470"}.ion-ios-nutrition-outline:before{content:"\f46f"}.ion-ios-paper:before{content:"\f472"}.ion-ios-paper-outline:before{content:"\f471"}.ion-ios-paperplane:before{content:"\f474"}.ion-ios-paperplane-outline:before{content:"\f473"}.ion-ios-partlysunny:before{content:"\f476"}.ion-ios-partlysunny-outline:before{content:"\f475"}.ion-ios-pause:before{content:"\f478"}.ion-ios-pause-outline:before{content:"\f477"}.ion-ios-paw:before{content:"\f47a"}.ion-ios-paw-outline:before{content:"\f479"}.ion-ios-people:before{content:"\f47c"}.ion-ios-people-outline:before{content:"\f47b"}.ion-ios-person:before{content:"\f47e"}.ion-ios-person-outline:before{content:"\f47d"}.ion-ios-personadd:before{content:"\f480"}.ion-ios-personadd-outline:before{content:"\f47f"}.ion-ios-photos:before{content:"\f482"}.ion-ios-photos-outline:before{content:"\f481"}.ion-ios-pie:before{content:"\f484"}.ion-ios-pie-outline:before{content:"\f483"}.ion-ios-pint:before{content:"\f486"}.ion-ios-pint-outline:before{content:"\f485"}.ion-ios-play:before{content:"\f488"}.ion-ios-play-outline:before{content:"\f487"}.ion-ios-plus:before{content:"\f48b"}.ion-ios-plus-empty:before{content:"\f489"}.ion-ios-plus-outline:before{content:"\f48a"}.ion-ios-pricetag:before{content:"\f48d"}.ion-ios-pricetag-outline:before{content:"\f48c"}.ion-ios-pricetags:before{content:"\f48f"}.ion-ios-pricetags-outline:before{content:"\f48e"}.ion-ios-printer:before{content:"\f491"}.ion-ios-printer-outline:before{content:"\f490"}.ion-ios-pulse:before{content:"\f493"}.ion-ios-pulse-strong:before{content:"\f492"}.ion-ios-rainy:before{content:"\f495"}.ion-ios-rainy-outline:before{content:"\f494"}.ion-ios-recording:before{content:"\f497"}.ion-ios-recording-outline:before{content:"\f496"}.ion-ios-redo:before{content:"\f499"}.ion-ios-redo-outline:before{content:"\f498"}.ion-ios-refresh:before{content:"\f49c"}.ion-ios-refresh-empty:before{content:"\f49a"}.ion-ios-refresh-outline:before{content:"\f49b"}.ion-ios-reload:before{content:"\f49d"}.ion-ios-reverse-camera:before{content:"\f49f"}.ion-ios-reverse-camera-outline:before{content:"\f49e"}.ion-ios-rewind:before{content:"\f4a1"}.ion-ios-rewind-outline:before{content:"\f4a0"}.ion-ios-rose:before{content:"\f4a3"}.ion-ios-rose-outline:before{content:"\f4a2"}.ion-ios-search:before{content:"\f4a5"}.ion-ios-search-strong:before{content:"\f4a4"}.ion-ios-settings:before{content:"\f4a7"}.ion-ios-settings-strong:before{content:"\f4a6"}.ion-ios-shuffle:before{content:"\f4a9"}.ion-ios-shuffle-strong:before{content:"\f4a8"}.ion-ios-skipbackward:before{content:"\f4ab"}.ion-ios-skipbackward-outline:before{content:"\f4aa"}.ion-ios-skipforward:before{content:"\f4ad"}.ion-ios-skipforward-outline:before{content:"\f4ac"}.ion-ios-snowy:before{content:"\f4ae"}.ion-ios-speedometer:before{content:"\f4b0"}.ion-ios-speedometer-outline:before{content:"\f4af"}.ion-ios-star:before{content:"\f4b3"}.ion-ios-star-half:before{content:"\f4b1"}.ion-ios-star-outline:before{content:"\f4b2"}.ion-ios-stopwatch:before{content:"\f4b5"}.ion-ios-stopwatch-outline:before{content:"\f4b4"}.ion-ios-sunny:before{content:"\f4b7"}.ion-ios-sunny-outline:before{content:"\f4b6"}.ion-ios-telephone:before{content:"\f4b9"}.ion-ios-telephone-outline:before{content:"\f4b8"}.ion-ios-tennisball:before{content:"\f4bb"}.ion-ios-tennisball-outline:before{content:"\f4ba"}.ion-ios-thunderstorm:before{content:"\f4bd"}.ion-ios-thunderstorm-outline:before{content:"\f4bc"}.ion-ios-time:before{content:"\f4bf"}.ion-ios-time-outline:before{content:"\f4be"}.ion-ios-timer:before{content:"\f4c1"}.ion-ios-timer-outline:before{content:"\f4c0"}.ion-ios-toggle:before{content:"\f4c3"}.ion-ios-toggle-outline:before{content:"\f4c2"}.ion-ios-trash:before{content:"\f4c5"}.ion-ios-trash-outline:before{content:"\f4c4"}.ion-ios-undo:before{content:"\f4c7"}.ion-ios-undo-outline:before{content:"\f4c6"}.ion-ios-unlocked:before{content:"\f4c9"}.ion-ios-unlocked-outline:before{content:"\f4c8"}.ion-ios-upload:before{content:"\f4cb"}.ion-ios-upload-outline:before{content:"\f4ca"}.ion-ios-videocam:before{content:"\f4cd"}.ion-ios-videocam-outline:before{content:"\f4cc"}.ion-ios-volume-high:before{content:"\f4ce"}.ion-ios-volume-low:before{content:"\f4cf"}.ion-ios-wineglass:before{content:"\f4d1"}.ion-ios-wineglass-outline:before{content:"\f4d0"}.ion-ios-world:before{content:"\f4d3"}.ion-ios-world-outline:before{content:"\f4d2"}.ion-ipad:before{content:"\f1f9"}.ion-iphone:before{content:"\f1fa"}.ion-ipod:before{content:"\f1fb"}.ion-jet:before{content:"\f295"}.ion-key:before{content:"\f296"}.ion-knife:before{content:"\f297"}.ion-laptop:before{content:"\f1fc"}.ion-leaf:before{content:"\f1fd"}.ion-levels:before{content:"\f298"}.ion-lightbulb:before{content:"\f299"}.ion-link:before{content:"\f1fe"}.ion-load-a:before{content:"\f29a"}.ion-load-b:before{content:"\f29b"}.ion-load-c:before{content:"\f29c"}.ion-load-d:before{content:"\f29d"}.ion-location:before{content:"\f1ff"}.ion-lock-combination:before{content:"\f4d4"}.ion-locked:before{content:"\f200"}.ion-log-in:before{content:"\f29e"}.ion-log-out:before{content:"\f29f"}.ion-loop:before{content:"\f201"}.ion-magnet:before{content:"\f2a0"}.ion-male:before{content:"\f2a1"}.ion-man:before{content:"\f202"}.ion-map:before{content:"\f203"}.ion-medkit:before{content:"\f2a2"}.ion-merge:before{content:"\f33f"}.ion-mic-a:before{content:"\f204"}.ion-mic-b:before{content:"\f205"}.ion-mic-c:before{content:"\f206"}.ion-minus:before{content:"\f209"}.ion-minus-circled:before{content:"\f207"}.ion-minus-round:before{content:"\f208"}.ion-model-s:before{content:"\f2c1"}.ion-monitor:before{content:"\f20a"}.ion-more:before{content:"\f20b"}.ion-mouse:before{content:"\f340"}.ion-music-note:before{content:"\f20c"}.ion-navicon:before{content:"\f20e"}.ion-navicon-round:before{content:"\f20d"}.ion-navigate:before{content:"\f2a3"}.ion-network:before{content:"\f341"}.ion-no-smoking:before{content:"\f2c2"}.ion-nuclear:before{content:"\f2a4"}.ion-outlet:before{content:"\f342"}.ion-paintbrush:before{content:"\f4d5"}.ion-paintbucket:before{content:"\f4d6"}.ion-paper-airplane:before{content:"\f2c3"}.ion-paperclip:before{content:"\f20f"}.ion-pause:before{content:"\f210"}.ion-person:before{content:"\f213"}.ion-person-add:before{content:"\f211"}.ion-person-stalker:before{content:"\f212"}.ion-pie-graph:before{content:"\f2a5"}.ion-pin:before{content:"\f2a6"}.ion-pinpoint:before{content:"\f2a7"}.ion-pizza:before{content:"\f2a8"}.ion-plane:before{content:"\f214"}.ion-planet:before{content:"\f343"}.ion-play:before{content:"\f215"}.ion-playstation:before{content:"\f30a"}.ion-plus:before{content:"\f218"}.ion-plus-circled:before{content:"\f216"}.ion-plus-round:before{content:"\f217"}.ion-podium:before{content:"\f344"}.ion-pound:before{content:"\f219"}.ion-power:before{content:"\f2a9"}.ion-pricetag:before{content:"\f2aa"}.ion-pricetags:before{content:"\f2ab"}.ion-printer:before{content:"\f21a"}.ion-pull-request:before{content:"\f345"}.ion-qr-scanner:before{content:"\f346"}.ion-quote:before{content:"\f347"}.ion-radio-waves:before{content:"\f2ac"}.ion-record:before{content:"\f21b"}.ion-refresh:before{content:"\f21c"}.ion-reply:before{content:"\f21e"}.ion-reply-all:before{content:"\f21d"}.ion-ribbon-a:before{content:"\f348"}.ion-ribbon-b:before{content:"\f349"}.ion-sad:before{content:"\f34a"}.ion-sad-outline:before{content:"\f4d7"}.ion-scissors:before{content:"\f34b"}.ion-search:before{content:"\f21f"}.ion-settings:before{content:"\f2ad"}.ion-share:before{content:"\f220"}.ion-shuffle:before{content:"\f221"}.ion-skip-backward:before{content:"\f222"}.ion-skip-forward:before{content:"\f223"}.ion-social-android:before{content:"\f225"}.ion-social-android-outline:before{content:"\f224"}.ion-social-angular:before{content:"\f4d9"}.ion-social-angular-outline:before{content:"\f4d8"}.ion-social-apple:before{content:"\f227"}.ion-social-apple-outline:before{content:"\f226"}.ion-social-bitcoin:before{content:"\f2af"}.ion-social-bitcoin-outline:before{content:"\f2ae"}.ion-social-buffer:before{content:"\f229"}.ion-social-buffer-outline:before{content:"\f228"}.ion-social-chrome:before{content:"\f4db"}.ion-social-chrome-outline:before{content:"\f4da"}.ion-social-codepen:before{content:"\f4dd"}.ion-social-codepen-outline:before{content:"\f4dc"}.ion-social-css3:before{content:"\f4df"}.ion-social-css3-outline:before{content:"\f4de"}.ion-social-designernews:before{content:"\f22b"}.ion-social-designernews-outline:before{content:"\f22a"}.ion-social-dribbble:before{content:"\f22d"}.ion-social-dribbble-outline:before{content:"\f22c"}.ion-social-dropbox:before{content:"\f22f"}.ion-social-dropbox-outline:before{content:"\f22e"}.ion-social-euro:before{content:"\f4e1"}.ion-social-euro-outline:before{content:"\f4e0"}.ion-social-facebook:before{content:"\f231"}.ion-social-facebook-outline:before{content:"\f230"}.ion-social-foursquare:before{content:"\f34d"}.ion-social-foursquare-outline:before{content:"\f34c"}.ion-social-freebsd-devil:before{content:"\f2c4"}.ion-social-github:before{content:"\f233"}.ion-social-github-outline:before{content:"\f232"}.ion-social-google:before{content:"\f34f"}.ion-social-google-outline:before{content:"\f34e"}.ion-social-googleplus:before{content:"\f235"}.ion-social-googleplus-outline:before{content:"\f234"}.ion-social-hackernews:before{content:"\f237"}.ion-social-hackernews-outline:before{content:"\f236"}.ion-social-html5:before{content:"\f4e3"}.ion-social-html5-outline:before{content:"\f4e2"}.ion-social-instagram:before{content:"\f351"}.ion-social-instagram-outline:before{content:"\f350"}.ion-social-javascript:before{content:"\f4e5"}.ion-social-javascript-outline:before{content:"\f4e4"}.ion-social-linkedin:before{content:"\f239"}.ion-social-linkedin-outline:before{content:"\f238"}.ion-social-markdown:before{content:"\f4e6"}.ion-social-nodejs:before{content:"\f4e7"}.ion-social-octocat:before{content:"\f4e8"}.ion-social-pinterest:before{content:"\f2b1"}.ion-social-pinterest-outline:before{content:"\f2b0"}.ion-social-python:before{content:"\f4e9"}.ion-social-reddit:before{content:"\f23b"}.ion-social-reddit-outline:before{content:"\f23a"}.ion-social-rss:before{content:"\f23d"}.ion-social-rss-outline:before{content:"\f23c"}.ion-social-sass:before{content:"\f4ea"}.ion-social-skype:before{content:"\f23f"}.ion-social-skype-outline:before{content:"\f23e"}.ion-social-snapchat:before{content:"\f4ec"}.ion-social-snapchat-outline:before{content:"\f4eb"}.ion-social-tumblr:before{content:"\f241"}.ion-social-tumblr-outline:before{content:"\f240"}.ion-social-tux:before{content:"\f2c5"}.ion-social-twitch:before{content:"\f4ee"}.ion-social-twitch-outline:before{content:"\f4ed"}.ion-social-twitter:before{content:"\f243"}.ion-social-twitter-outline:before{content:"\f242"}.ion-social-usd:before{content:"\f353"}.ion-social-usd-outline:before{content:"\f352"}.ion-social-vimeo:before{content:"\f245"}.ion-social-vimeo-outline:before{content:"\f244"}.ion-social-whatsapp:before{content:"\f4f0"}.ion-social-whatsapp-outline:before{content:"\f4ef"}.ion-social-windows:before{content:"\f247"}.ion-social-windows-outline:before{content:"\f246"}.ion-social-wordpress:before{content:"\f249"}.ion-social-wordpress-outline:before{content:"\f248"}.ion-social-yahoo:before{content:"\f24b"}.ion-social-yahoo-outline:before{content:"\f24a"}.ion-social-yen:before{content:"\f4f2"}.ion-social-yen-outline:before{content:"\f4f1"}.ion-social-youtube:before{content:"\f24d"}.ion-social-youtube-outline:before{content:"\f24c"}.ion-soup-can:before{content:"\f4f4"}.ion-soup-can-outline:before{content:"\f4f3"}.ion-speakerphone:before{content:"\f2b2"}.ion-speedometer:before{content:"\f2b3"}.ion-spoon:before{content:"\f2b4"}.ion-star:before{content:"\f24e"}.ion-stats-bars:before{content:"\f2b5"}.ion-steam:before{content:"\f30b"}.ion-stop:before{content:"\f24f"}.ion-thermometer:before{content:"\f2b6"}.ion-thumbsdown:before{content:"\f250"}.ion-thumbsup:before{content:"\f251"}.ion-toggle:before{content:"\f355"}.ion-toggle-filled:before{content:"\f354"}.ion-transgender:before{content:"\f4f5"}.ion-trash-a:before{content:"\f252"}.ion-trash-b:before{content:"\f253"}.ion-trophy:before{content:"\f356"}.ion-tshirt:before{content:"\f4f7"}.ion-tshirt-outline:before{content:"\f4f6"}.ion-umbrella:before{content:"\f2b7"}.ion-university:before{content:"\f357"}.ion-unlocked:before{content:"\f254"}.ion-upload:before{content:"\f255"}.ion-usb:before{content:"\f2b8"}.ion-videocamera:before{content:"\f256"}.ion-volume-high:before{content:"\f257"}.ion-volume-low:before{content:"\f258"}.ion-volume-medium:before{content:"\f259"}.ion-volume-mute:before{content:"\f25a"}.ion-wand:before{content:"\f358"}.ion-waterdrop:before{content:"\f25b"}.ion-wifi:before{content:"\f25c"}.ion-wineglass:before{content:"\f2b9"}.ion-woman:before{content:"\f25d"}.ion-wrench:before{content:"\f2ba"}.ion-xbox:before{content:"\f30c"} 12 | -------------------------------------------------------------------------------- /public/vendor/prism-base16-brewer.light.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | 4 | Name: Base16 Brewer Light 5 | Author: Timothée Poisot (http://github.com/tpoisot) 6 | 7 | Prism template by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/prism/) 8 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 9 | 10 | */ 11 | code[class*="language-"], 12 | pre[class*="language-"] { 13 | font-family: Consolas, Menlo, Monaco, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", "Courier New", Courier, monospace; 14 | font-size: 14px; 15 | line-height: 1.375; 16 | direction: ltr; 17 | text-align: left; 18 | white-space: pre; 19 | word-spacing: normal; 20 | word-break: normal; 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | -webkit-hyphens: none; 25 | -ms-hyphens: none; 26 | hyphens: none; 27 | background: #fcfdfe; 28 | color: #515253; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #dadbdc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #dadbdc; 41 | } 42 | 43 | /* Code blocks */ 44 | pre[class*="language-"] { 45 | padding: 1em; 46 | margin: .5em 0; 47 | overflow: auto; 48 | } 49 | 50 | /* Inline code */ 51 | :not(pre) > code[class*="language-"] { 52 | padding: .1em; 53 | border-radius: .3em; 54 | } 55 | 56 | .token.comment, 57 | .token.prolog, 58 | .token.doctype, 59 | .token.cdata { 60 | color: #959697; 61 | } 62 | 63 | .token.punctuation { 64 | color: #515253; 65 | } 66 | 67 | .token.namespace { 68 | opacity: .7; 69 | } 70 | 71 | .token.operator, 72 | .token.boolean, 73 | .token.number { 74 | color: #e6550d; 75 | } 76 | 77 | .token.property { 78 | color: #dca060; 79 | } 80 | 81 | .token.tag { 82 | color: #3182bd; 83 | } 84 | 85 | .token.string { 86 | color: #80b1d3; 87 | } 88 | 89 | .token.selector { 90 | color: #756bb1; 91 | } 92 | 93 | .token.attr-name { 94 | color: #e6550d; 95 | } 96 | 97 | .token.entity, 98 | .token.url, 99 | .language-css .token.string, 100 | .style .token.string { 101 | color: #80b1d3; 102 | } 103 | 104 | .token.attr-value, 105 | .token.keyword, 106 | .token.control, 107 | .token.directive, 108 | .token.unit { 109 | color: #31a354; 110 | } 111 | 112 | .token.statement, 113 | .token.regex, 114 | .token.atrule { 115 | color: #80b1d3; 116 | } 117 | 118 | .token.placeholder, 119 | .token.variable { 120 | color: #3182bd; 121 | } 122 | 123 | .token.deleted { 124 | text-decoration: line-through; 125 | } 126 | 127 | .token.inserted { 128 | border-bottom: 1px dotted #0c0d0e; 129 | text-decoration: none; 130 | } 131 | 132 | .token.italic { 133 | font-style: italic; 134 | } 135 | 136 | .token.important, 137 | .token.bold { 138 | font-weight: bold; 139 | } 140 | 141 | .token.important { 142 | color: #e31a1c; 143 | } 144 | 145 | .token.entity { 146 | cursor: help; 147 | } 148 | 149 | pre > code.highlight { 150 | outline: 0.4em solid #e31a1c; 151 | outline-offset: .4em; 152 | } 153 | 154 | .line-numbers .line-numbers-rows { 155 | border-right-color: #dadbdc !important; 156 | } 157 | 158 | .line-numbers-rows > span:before { 159 | color: #b7b8b9 !important; 160 | } 161 | 162 | .line-highlight { 163 | background: rgba(12, 13, 14, 0.2) !important; 164 | background: -webkit-linear-gradient(left, rgba(12, 13, 14, 0.2) 70%, rgba(12, 13, 14, 0)) !important; 165 | background: linear-gradient(to right, rgba(12, 13, 14, 0.2) 70%, rgba(12, 13, 14, 0)) !important; 166 | } 167 | -------------------------------------------------------------------------------- /screenshots/monitor-feed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/stripe-webhook-monitor/cb304fd504cac54dd0f117a61465ad7d9e9e6635/screenshots/monitor-feed.gif -------------------------------------------------------------------------------- /screenshots/monitor-graph.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/stripe-webhook-monitor/cb304fd504cac54dd0f117a61465ad7d9e9e6635/screenshots/monitor-graph.gif -------------------------------------------------------------------------------- /screenshots/setting-up-webhooks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe-archive/stripe-webhook-monitor/cb304fd504cac54dd0f117a61465ad7d9e9e6635/screenshots/setting-up-webhooks.png -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const config = require("./config.js"); 4 | const boxen = require("boxen"); 5 | const stripe = require("stripe")(config.stripe.secretKey); 6 | const express = require("express"); 7 | const socketio = require("socket.io"); 8 | const http = require("http"); 9 | const bodyParser = require("body-parser"); 10 | const path = require("path"); 11 | const chalk = require("chalk"); 12 | 13 | /* 14 | We use two different Express servers for security reasons: our webhooks 15 | endpoint needs to be publicly accessible, but we don't want our monitoring 16 | dashboard to be publicly accessible since it may contain sensitive data. 17 | */ 18 | 19 | // The first Express server will serve Stripe Monitor (on a different port). 20 | const monitor = express(); 21 | const monitorServer = http.Server(monitor); 22 | // We'll set up Socket.io to notify us of new events 23 | const io = socketio(monitorServer); 24 | let recentEvents = []; 25 | 26 | // Serve static files and start the server 27 | monitor.use(express.static(path.join(__dirname, "public"))); 28 | monitorServer.listen(config.port, () => { 29 | console.log(`Stripe Monitor is up: http://localhost:${config.port}`); 30 | }); 31 | 32 | // Provides environment details: the Dashboard URL will vary based on whether we're in test or live mode 33 | monitor.get("/environment", async (req, res) => { 34 | let dashboardUrl = "https://dashboard.stripe.com/"; 35 | if (config.stripe.secretKey.startsWith("sk_test")) { 36 | dashboardUrl += "test/"; 37 | } 38 | res.send({ dashboardUrl }); 39 | }); 40 | 41 | // Provides the 20 most recent events (useful when the app first loads) 42 | monitor.get("/recent-events", async (req, res) => { 43 | let response = await stripe.events.list({ limit: 20 }); 44 | recentEvents = response.data; 45 | res.send(recentEvents); 46 | }); 47 | 48 | // The second Express server will receive webhooks 49 | const webhooks = express(); 50 | const webhooksPort = config.port + 1; 51 | 52 | webhooks.use(bodyParser.json()); 53 | webhooks.listen(webhooksPort, () => { 54 | console.log(`Listening for webhooks: http://localhost:${webhooksPort}`); 55 | }); 56 | 57 | // Provides an endpoint to receive webhooks 58 | webhooks.post("/", async (req, res) => { 59 | let event = req.body; 60 | // Send a notification that we have a new event 61 | // Here we're using Socket.io, but server-sent events or another mechanism can be used. 62 | io.emit("event", event); 63 | // Stripe needs to receive a 200 status from any webhooks endpoint 64 | res.sendStatus(200); 65 | }); 66 | 67 | // Use ngrok to provide a public URL for receiving webhooks 68 | if (config.ngrok.enabled) { 69 | const ngrok = require("ngrok"); 70 | const boxen = require("boxen"); 71 | 72 | ngrok.connect( 73 | { 74 | addr: webhooksPort, 75 | subdomain: config.ngrok.subdomain, 76 | authtoken: config.ngrok.authtoken 77 | }, 78 | function(err, url) { 79 | if (err) { 80 | console.log(err); 81 | if (err.code === "ECONNREFUSED") { 82 | console.log( 83 | chalk.red(`Connection refused at ${err.address}:${err.port}`) 84 | ); 85 | process.exit(1); 86 | } 87 | console.log(chalk.yellow(`ngrok reported an error: ${err.msg}`)); 88 | console.log( 89 | boxen(err.details.err.trim(), { 90 | padding: { top: 0, right: 2, bottom: 0, left: 2 } 91 | }) 92 | ); 93 | } 94 | console.log(` └ Public URL for receiving Stripe webhooks: ${url}`); 95 | } 96 | ); 97 | } 98 | --------------------------------------------------------------------------------