remove JWT & replace React with Mithril

This commit is contained in:
Danny van Kooten 2016-11-23 15:51:19 +01:00
parent 933884c401
commit f5a25c7c28
14 changed files with 273 additions and 397 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules
static
.*
storage

3
ana.go
View File

@ -24,8 +24,9 @@ func main() {
r := mux.NewRouter()
// register routes
r.Handle("/token", api.GetTokenHandler).Methods("GET")
r.HandleFunc("/collect", api.CollectHandler).Methods("GET")
r.Handle("/api/session", api.Login).Methods("POST")
r.Handle("/api/session", api.Logout).Methods("DELETE")
r.Handle("/api/visits/count/day", api.Authorize(api.GetVisitsDayCountHandler)).Methods("GET")
r.Handle("/api/visits/count/realtime", api.Authorize(api.GetVisitsRealtimeCount)).Methods("GET")
r.Handle("/api/visits", api.Authorize(api.GetVisitsHandler)).Methods("GET")

View File

@ -2,50 +2,46 @@ package api
import (
"net/http"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
"os"
"time"
"github.com/gorilla/sessions"
)
var store = sessions.NewFilesystemStore( "./storage/sessions/", []byte("something-very-secret"))
// URL: POST /api/session
var Login = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "auth")
session.Values["user"] = "Danny"
err := session.Save(r, w)
checkError(err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte("true"))
})
// URL: DELETE /api/session
var Logout = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "auth")
session.Options.MaxAge = -1
err := session.Save(r, w)
checkError(err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte("true"))
})
/* middleware */
func Authorize(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor, keyLookupFunc)
session, err := store.Get(r, "auth")
checkError(err)
if err == nil && token.Valid {
next.ServeHTTP(w, r)
if user, ok := session.Values["user"]; !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
}
w.WriteHeader(http.StatusUnauthorized)
next.ServeHTTP(w, r)
})
}
func getSigningKey() []byte {
return []byte(os.Getenv("APP_SECRET_KEY"))
}
/* Handlers */
var GetTokenHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
// TODO: Check with database here.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"admin": true,
"name": "Danny",
"exp": time.Now().Add(time.Hour * 24).Unix(),
})
/* Sign the token with our secret */
tokenString, _ := token.SignedString(getSigningKey())
/* Finally, write the token to the browser window */
w.Write([]byte(tokenString))
})
func keyLookupFunc(t *jwt.Token) (interface{}, error) {
// Look up key in database.
// TODO
//
return getSigningKey(), nil
}

View File

@ -1,20 +1,55 @@
import React, { Component } from 'react'
import m from 'mithril';
class Login extends React.Component {
constructor(props) {
super(props);
}
function handleSubmit(e) {
e.preventDefault();
render() {
fetch('/api/session', {
method: "POST",
data: {
email: this.data.email(),
password: this.data.password()
},
credentials: 'include'
}).then((r) => {
if( r.status == 200 ) {
this.onAuth();
console.log("Authenticated!");
}
return (
<div className="block">
<h2>Login</h2>
<p>
<a href="">Sign in</a>
</p>
</div>
);
// TODO: Handle errors
});
}
const Login = {
controller(args) {
this.onAuth = args.onAuth;
this.data = {
email: m.prop(''),
password: m.prop(''),
}
this.onSubmit = handleSubmit.bind(this);
},
view(c) {
return m('div.block', [
m('h2', 'Login'),
m('form', {
method: "POST",
onsubmit: c.onSubmit
}, [
m('div.form-group', [
m('label', 'Email address'),
m('input', { type: "email", onchange: m.withAttr("value", c.data.email ) }),
]),
m('div.form-group', [
m('label', 'Password'),
m('input', { type: "password", onchange: m.withAttr("value", c.data.password ) }),
]),
m('div.form-group', [
m('input', { type: "submit", value: "Sign in" }),
]),
])
])
}
}

View File

@ -1,47 +1,49 @@
import React, { Component } from 'react'
import m from 'mithril';
class PageviewsList extends React.Component {
constructor(props) {
super(props);
this.state = { records: [] };
this.refresh() && window.setInterval(this.refresh.bind(this), 60000);
}
function fetchRecords() {
return fetch('/api/pageviews', {
credentials: 'include'
}).then((r) => {
if( r.ok ) {
return r.json();
}
}).then((data) => {
m.startComputation();
this.records(data);
m.endComputation();
});
}
refresh() {
return fetch('/api/pageviews')
.then((r) => r.json())
.then((data) => {
this.setState({records: data});
});
}
const Pageviews = {
controller() {
this.records = m.prop([]);
fetchRecords.call(this) && window.setInterval(fetchRecords.bind(this), 60000);
},
view(c) {
const tableRows = c.records().map((p, i) => m('tr', [
m('td', i+1),
m('td', [
m('a', { href: p.Path }, p.Path)
]),
m('td', p.Count),
m('td', p.CountUnique)
]));
render() {
const tableRows = this.state.records.map((p, i) =>
<tr key={i}>
<td>{i+1}</td>
<td><a href="{p.Path}">{p.Path}</a></td>
<td>{p.Count}</td>
<td>{p.CountUnique}</td>
</tr>
);
return (
<div className="block">
<h2>Pageviews</h2>
<table className="table pageviews">
<thead>
<tr>
<th>#</th>
<th>URL</th>
<th>Pageviews</th>
<th>Unique</th>
</tr>
</thead>
<tbody>{tableRows}</tbody>
</table>
</div>
);
return m('div.block', [
m('h2', 'Pageviews'),
m('table.table.pageviews', [
m('thead', [
m('tr', [
m('th', '#'),
m('th', 'URL'),
m('th', 'Pageviews'),
m('th', 'Unique'),
]) // tr
]), // thead
m('tbody', tableRows )
]) // table
])
}
}
export default PageviewsList
export default Pageviews

View File

@ -1,31 +0,0 @@
'use strict';
import React, { Component } from 'react'
class RealtimeVisitsCount extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.refresh() && window.setInterval(this.refresh.bind(this), 5000);
}
refresh() {
return fetch('/api/visits/count/realtime')
.then((r) => r.json())
.then((data) => {
this.setState({count: data});
});
}
render() {
let visitors = this.state.count > 1 ? 'visitors' : 'visitor';
return (
<div className="block">
<span className="count">{this.state.count}</span> <span>{visitors} on the site right now.</span>
</div>
);
}
}
export default RealtimeVisitsCount;

View File

@ -0,0 +1,33 @@
'use strict';
import m from 'mithril';
function fetchData() {
return fetch('/api/visits/count/realtime', {
credentials: 'include'
})
.then((r) => r.json())
.then((data) => {
this.count = data;
m.redraw();
});
}
const RealtimeVisits = {
controller(args) {
this.count = 0;
fetchData.bind(this) && window.setInterval(fetchData.bind(this), 6000);
},
view(c) {
let visitors = c.count > 1 ? 'visitors' : 'visitor';
return m('div.block', [
m('span.count', c.count),
' ',
m('span', visitors + " on the site right now.")
])
}
}
export default RealtimeVisits

View File

@ -1,57 +1,61 @@
'use strict';
'use strict'
import React, { Component } from 'react'
import m from 'mithril';
import Chart from 'chart.js'
Chart.defaults.global.tooltips.xPadding = 12;
Chart.defaults.global.tooltips.yPadding = 12;
//Chart.defaults.global.scales.yAxes.ticks.beginAtZero = true; // = 12;
function fetchData() {
return fetch('/api/visits/count/day', {
credentials: 'include'
})
.then((r) => r.json())
.then((data) => {
this.data = data;
initChart.call(this);
});
}
class VisitsGraph extends React.Component {
constructor(props) {
super(props);
this.state = { data: [] }
this.refresh();
}
function initChart() {
let ctx = this.chartCtx;
refresh() {
return fetch('/api/visits/count/day')
.then((r) => r.json())
.then((data) => {
this.setState({ data: data });
this.initChart(this.chartCtx);
});
}
initChart(ctx) {
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: this.state.data.map((d) => d.Label),
datasets: [{
label: '# of Visitors',
data: this.state.data.map((d) => d.Count),
backgroundColor: 'rgba(0, 155, 255, .2)'
}]
},
options: {
scale: {
ticks: {
beginAtZero: true
}
new Chart(ctx, {
type: 'line',
data: {
labels: this.data.map((d) => d.Label),
datasets: [{
label: '# of Visitors',
data: this.data.map((d) => d.Count),
backgroundColor: 'rgba(0, 155, 255, .2)'
}]
},
options: {
scale: {
ticks: {
beginAtZero: true
}
}
});
}
}
});
}
const VisitsGraph = {
controller(args) {
this.data = [];
fetchData.call(this);
},
view(c) {
return m('div.block', [
m('canvas', {
width: 600,
height: 200,
config: (el) => { c.chartCtx = el; }
})
])
},
render() {
return (
<div className="block">
<canvas ref={(ctx) => { this.chartCtx = ctx; }} width="600" height="200"></canvas>
</div>
);
}
}
export default VisitsGraph;

View File

@ -1,45 +0,0 @@
import React, { Component } from 'react'
class VisitsList extends React.Component {
constructor(props) {
super(props);
this.state = { records: [] };
this.refresh() && window.setInterval(this.refresh.bind(this), 60000);
}
refresh() {
return fetch('/api/visits')
.then((r) => r.json())
.then((data) => {
this.setState({records: data});
});
}
render() {
const tableRows = this.state.records.map((visit) =>
<tr key={visit.ID}>
<td>{visit.Timestamp}</td>
<td>{visit.IpAddress}</td>
<td>{visit.Path}</td>
</tr>
);
return (
<div className="block">
<h2>Visits</h2>
<table className="visits-table">
<thead>
<tr>
<th>When</th>
<th>IP Address</th>
<th>Path</th>
</tr>
</thead>
<tbody>{tableRows}</tbody>
</table>
</div>
);
}
}
export default VisitsList

View File

@ -1,42 +1,43 @@
'use strict';
import React from 'react';
import ReactDOM from 'react-dom';
import RealtimeVisitsCount from './components/realtime-visits.js';
import VisitsList from './components/visits-list.js';
import PageviewsList from './components/pageviews.js';
import VisitsGraph from './components/visits-graph.js';
const m = require('mithril');
import Login from './components/login.js';
import Pageviews from './components/pageviews.js';
import RealtimeVisits from './components/realtime.js';
import VisitsGraph from './components/visits-graph.js';
class App extends React.Component {
const App = {
controller(args) {
this.state = {
authenticated: false
};
constructor(props) {
super(props)
this.state = { idToken: null }
}
render() {
if(this.state.idToken) {
return (
<div className="container">
<h1>Ana</h1>
<RealtimeVisitsCount />
<VisitsGraph />
<PageviewsList />
</div>
);
} else {
return (
<div className="container">
<Login />
</div>
);
this.setState = function(nextState) {
m.startComputation();
for(var k in nextState) {
this.state[k] = nextState[k];
}
m.endComputation();
}
},
view(c) {
if( ! c.state.authenticated ) {
return m.component(Login, {
onAuth: () => { c.setState({ authenticated: true }) }
});
}
return [
m('div.container', [
m('h1', 'Ana'),
m.component(RealtimeVisits),
m.component(VisitsGraph),
m.component(Pageviews),
])
]
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
m.mount(document.getElementById('root'), App)

View File

@ -2,7 +2,7 @@
body {
font-family: Arial, Verdana, sans-serif;
font-size: 14px;
font-size: 15px;
color: #333;
background: #f5f5f5;
}
@ -11,8 +11,6 @@ h1, h2, h3 {
color: #111;
}
table {
border-collapse: collapse;
@ -26,6 +24,18 @@ a {
color: #09f;
}
label {
display: block;
}
input {
padding: 8px;
&[type="submit"] {
cursor: pointer;
}
}
.container {
max-width: 1020px;
padding: 0 20px;
@ -42,6 +52,10 @@ a {
}
}
.form-group {
margin-bottom: 20px;
}
.count {
font-weight: bold;
font-size: 120%;

View File

@ -18,9 +18,12 @@ gulp.task('browserify', function () {
entries: './assets/js/script.js',
debug: true
})
.transform("babelify", {presets: ["es2015", "react"]})
.on('error', gutil.log)
.transform("babelify", {presets: ["es2015"]})
.bundle()
.on('error', function(err){
console.log(err.message);
this.emit('end');
})
.pipe(source('script.js'))
.pipe(buffer())
.pipe(gulp.dest('./static/js/'))
@ -30,6 +33,7 @@ gulp.task('sass', function () {
var files = './assets/sass/[^_]*.scss';
return gulp.src(files)
.pipe(sass())
.on('error', gutil.log)
.pipe(rename({ extname: '.css' }))
.pipe(gulp.dest('./static/css'))
});

View File

@ -4,16 +4,14 @@
"gulp": "^3.9.1",
"gulp-rename": "^1.2.2",
"gulp-sass": "^2.3.2",
"react": "^15.4.0",
"gulp-util": "^3.0.7",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0"
},
"dependencies": {
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babelify": "^7.3.0",
"chart.js": "^2.4.0",
"gulp-util": "^3.0.7",
"react-dom": "^15.4.0"
"mithril": "^0.2.5"
}
}

149
yarn.lock
View File

@ -84,10 +84,6 @@ array-unique@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
asap@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"
asn1.js@^4.0.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.0.tgz#f71a1243f3e79d46d7b07d7fbf4824ee73af054a"
@ -180,15 +176,6 @@ babel-generator@^6.18.0:
lodash "^4.2.0"
source-map "^0.5.0"
babel-helper-builder-react-jsx@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.18.0.tgz#ab02f19a2eb7ace936dd87fa55896d02be59bf71"
dependencies:
babel-runtime "^6.9.0"
babel-types "^6.18.0"
esutils "^2.0.0"
lodash "^4.2.0"
babel-helper-call-delegate@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.18.0.tgz#05b14aafa430884b034097ef29e9f067ea4133bd"
@ -276,14 +263,6 @@ babel-plugin-check-es2015-constants@^6.3.13:
dependencies:
babel-runtime "^6.0.0"
babel-plugin-syntax-flow@^6.18.0, babel-plugin-syntax-flow@^6.3.13:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
babel-plugin-transform-es2015-arrow-functions@^6.3.13:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.8.0.tgz#5b63afc3181bdc9a8c4d481b5a4f3f7d7fef3d9d"
@ -453,41 +432,6 @@ babel-plugin-transform-es2015-unicode-regex@^6.3.13:
babel-runtime "^6.0.0"
regexpu-core "^2.0.0"
babel-plugin-transform-flow-strip-types@^6.3.13:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.18.0.tgz#4d3e642158661e9b40db457c004a30817fa32592"
dependencies:
babel-plugin-syntax-flow "^6.18.0"
babel-runtime "^6.0.0"
babel-plugin-transform-react-display-name@^6.3.13:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.8.0.tgz#f7a084977383d728bdbdc2835bba0159577f660e"
dependencies:
babel-runtime "^6.0.0"
babel-plugin-transform-react-jsx-self@^6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.11.0.tgz#605c9450c1429f97a930f7e1dfe3f0d9d0dbd0f4"
dependencies:
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.9.0"
babel-plugin-transform-react-jsx-source@^6.3.13:
version "6.9.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.9.0.tgz#af684a05c2067a86e0957d4f343295ccf5dccf00"
dependencies:
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.9.0"
babel-plugin-transform-react-jsx@^6.3.13:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.8.0.tgz#94759942f70af18c617189aa7f3593f1644a71ab"
dependencies:
babel-helper-builder-react-jsx "^6.8.0"
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.0.0"
babel-plugin-transform-regenerator@^6.16.0:
version "6.16.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.16.1.tgz#a75de6b048a14154aae14b0122756c5bed392f59"
@ -532,18 +476,6 @@ babel-preset-es2015:
babel-plugin-transform-es2015-unicode-regex "^6.3.13"
babel-plugin-transform-regenerator "^6.16.0"
babel-preset-react:
version "6.16.0"
resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.16.0.tgz#aa117d60de0928607e343c4828906e4661824316"
dependencies:
babel-plugin-syntax-flow "^6.3.13"
babel-plugin-syntax-jsx "^6.3.13"
babel-plugin-transform-flow-strip-types "^6.3.13"
babel-plugin-transform-react-display-name "^6.3.13"
babel-plugin-transform-react-jsx "^6.3.13"
babel-plugin-transform-react-jsx-self "^6.11.0"
babel-plugin-transform-react-jsx-source "^6.3.13"
babel-register@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.18.0.tgz#892e2e03865078dd90ad2c715111ec4449b32a68"
@ -955,10 +887,6 @@ convert-source-map@~1.1.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
core-js@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
@ -1153,12 +1081,6 @@ elliptic@^6.0.0:
hash.js "^1.0.0"
inherits "^2.0.1"
encoding@^0.1.11:
version "0.1.12"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
dependencies:
iconv-lite "~0.4.13"
end-of-stream@~0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf"
@ -1197,7 +1119,7 @@ escape-string-regexp@^1.0.2:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
esutils@^2.0.0, esutils@^2.0.2:
esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@ -1250,17 +1172,6 @@ fancy-log@^1.1.0:
chalk "^1.1.1"
time-stamp "^1.0.0"
fbjs@^0.8.1, fbjs@^0.8.4:
version "0.8.6"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.6.tgz#7eb67d6986b2d5007a9b6e92e0e7cb6f75cad290"
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
ua-parser-js "^0.7.9"
filename-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
@ -1698,10 +1609,6 @@ https-browserify@~0.0.0:
version "0.0.1"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
iconv-lite@~0.4.13:
version "0.4.14"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.14.tgz#0c4b78106835ecce149ffc7f1b588a9f23bf28e3"
ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
@ -1866,10 +1773,6 @@ is-relative@^0.2.1:
dependencies:
is-unc-path "^0.1.1"
is-stream@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@ -1906,13 +1809,6 @@ isobject@^2.0.0:
dependencies:
isarray "1.0.0"
isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@ -2165,7 +2061,7 @@ lodash@~4.16.4:
version "4.16.6"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777"
loose-envify@^1.0.0, loose-envify@^1.1.0:
loose-envify@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.0.tgz#6b26248c42f6d4fa4b0d8542f78edfcde35642a8"
dependencies:
@ -2278,6 +2174,10 @@ minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
mithril:
version "0.2.5"
resolved "https://registry.yarnpkg.com/mithril/-/mithril-0.2.5.tgz#c1a50438a93ac23f11ada91188bb784c755404c2"
mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0":
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@ -2326,13 +2226,6 @@ natives@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31"
node-fetch@^1.0.1:
version "1.6.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
node-gyp@^3.3.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.4.0.tgz#dda558393b3ecbbe24c9e6b8703c71194c63fa36"
@ -2607,12 +2500,6 @@ process@~0.11.0:
version "0.11.9"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1"
promise@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf"
dependencies:
asap "~2.0.3"
pseudomap@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@ -2658,22 +2545,6 @@ randombytes@^2.0.0, randombytes@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
react:
version "15.4.0"
resolved "https://registry.yarnpkg.com/react/-/react-15.4.0.tgz#736c1c7c542e8088127106e1f450b010f86d172b"
dependencies:
fbjs "^0.8.4"
loose-envify "^1.1.0"
object-assign "^4.1.0"
react-dom:
version "15.4.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.4.0.tgz#6a97a69000966570db48c746bc4b7b0ca50d1534"
dependencies:
fbjs "^0.8.1"
loose-envify "^1.1.0"
object-assign "^4.1.0"
read-only-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0"
@ -3129,10 +3000,6 @@ typedarray@~0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
ua-parser-js@^0.7.9:
version "0.7.12"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
umd@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.1.tgz#8ae556e11011f63c2596708a8837259f01b3d60e"
@ -3243,10 +3110,6 @@ vm-browserify@~0.0.1:
dependencies:
indexof "0.0.1"
whatwg-fetch@>=0.10.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.1.tgz#078b9461bbe91cea73cbce8bb122a05f9e92b772"
which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"