Skip to content

Commit 3d21cb3

Browse files
Merge pull request #59 from conveyal/dev
1.2.0 Add react utils
2 parents 50fae0d + 60e2ea7 commit 3d21cb3

13 files changed

+617
-33
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ build/Release
2626

2727
# Dependency directory
2828
node_modules
29+
/react
2930

3031
# Optional npm cache directory
3132
.npm

lib/react/auth0.js

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import Auth0Lock from 'auth0-lock'
2+
import Auth0Client from 'auth0-js'
3+
import {Component, PropTypes} from 'react'
4+
import {connect} from 'react-redux'
5+
import {push} from 'react-router-redux'
6+
import {createAction} from 'redux-actions'
7+
8+
const {AUTH0_CLIENT_ID, AUTH0_DOMAIN} = process.env
9+
const localStorage = window.localStorage
10+
11+
export const setAuth0User = createAction('set auth0 user')
12+
export const authIsRequired = AUTH0_CLIENT_ID && AUTH0_DOMAIN
13+
export const lock = authIsRequired
14+
? new Auth0Lock(
15+
AUTH0_CLIENT_ID,
16+
AUTH0_DOMAIN, {
17+
auth: {
18+
params: {
19+
scope: 'openid analyst offline_access'
20+
},
21+
redirect: false
22+
},
23+
closeable: false,
24+
autoclose: true
25+
})
26+
: null
27+
export const client = authIsRequired
28+
? new Auth0Client({
29+
clientID: AUTH0_CLIENT_ID,
30+
domain: AUTH0_DOMAIN
31+
})
32+
: null
33+
34+
/**
35+
* Use on application mount when authentication is required
36+
*/
37+
export function refreshUser (dispatch) {
38+
if (authIsRequired) {
39+
const userString = localStorage.getItem('user')
40+
const user = userString && JSON.parse(userString)
41+
if (user && user.refreshToken) {
42+
dispatch(setAuth0User(user))
43+
if (process.env.NODE_ENV !== 'development') {
44+
client.refreshToken(user.refreshToken, function (err, delegationResult) {
45+
if (err) {
46+
dispatch(setAuth0User(null))
47+
localStorage.setItem('user', null)
48+
dispatch(push('/login'))
49+
} else {
50+
user.idToken = delegationResult.id_token
51+
dispatch(setAuth0User({idToken: delegationResult.id_token}))
52+
localStorage.setItem('user', JSON.stringify(user))
53+
}
54+
})
55+
}
56+
} else {
57+
dispatch(push('/login'))
58+
}
59+
}
60+
}
61+
62+
class Auth0 extends Component {
63+
static propTypes = {
64+
push: PropTypes.func.isRequired,
65+
setAuth0User: PropTypes.func.isRequired
66+
}
67+
68+
_authenticated = (authResult) => {
69+
const {push, setAuth0User} = this.props
70+
lock.getProfile(authResult.idToken, (error, profile) => {
71+
if (error) {
72+
setAuth0User(null)
73+
push('/login')
74+
} else {
75+
const user = {
76+
...authResult,
77+
profile
78+
}
79+
localStorage.setItem('user', JSON.stringify(user))
80+
setAuth0User(user)
81+
push('/')
82+
}
83+
})
84+
}
85+
86+
componentDidMount () {
87+
// when testing, auth0 credentials are not currently entered, so `lock` will be null
88+
if (lock) {
89+
lock.show()
90+
lock.on('authenticated', this._authenticated)
91+
}
92+
}
93+
94+
render () {
95+
return null
96+
}
97+
}
98+
99+
function mapStateToProps (state) {
100+
return state
101+
}
102+
const mapDispatchToProps = {
103+
push,
104+
setAuth0User
105+
}
106+
107+
export default connect(mapStateToProps, mapDispatchToProps)(Auth0)

lib/react/fetch.js

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import fetch from 'isomorphic-fetch'
2+
import isObject from 'lodash.isobject'
3+
import {createAction} from 'redux-actions'
4+
5+
export const INCREMENT_FETCH = 'increment outstanding fetches'
6+
export const DECREMENT_FETCH = 'decrement outstanding fetches'
7+
export const FETCH = 'fetch'
8+
export const FETCH_ERROR = 'fetch error'
9+
10+
const incrementFetches = createAction(INCREMENT_FETCH)
11+
const decrementFetches = createAction(DECREMENT_FETCH)
12+
const fetchError = createAction(FETCH_ERROR)
13+
const fetchAction = createAction(FETCH)
14+
15+
export function middleware (store) {
16+
return (next) => (action) =>
17+
action.type === FETCH
18+
? runFetch(action.payload, store.getState())
19+
: next(action)
20+
}
21+
22+
export default function createFetch ({
23+
next,
24+
options = {},
25+
url
26+
}) {
27+
return [
28+
incrementFetches(),
29+
fetchAction({url, options, next})
30+
]
31+
}
32+
33+
function runFetch ({
34+
next,
35+
options,
36+
url
37+
}, state) {
38+
const isJSON = isObject(options.body)
39+
return fetch(url, {
40+
...options,
41+
body: isJSON ? JSON.stringify(options.body) : options.body,
42+
headers: {
43+
...createAuthorizationHeader(state),
44+
...createContentHeader(isJSON),
45+
...(options.headers || {})
46+
}
47+
})
48+
.then(checkStatus)
49+
.then(createResponse)
50+
.then((response) => [decrementFetches(), next(response)])
51+
.catch((error) =>
52+
createErrorResponse(error)
53+
.then((response) => [decrementFetches(), fetchError(response), next(response, true)]))
54+
}
55+
56+
function createAuthorizationHeader (state) {
57+
return state.user && state.user.idToken
58+
? {Authorization: `bearer ${state.user.idToken}`}
59+
: {}
60+
}
61+
62+
function checkStatus (res) {
63+
if (res.status >= 200 && res.status < 300) {
64+
return res
65+
} else {
66+
throw res
67+
}
68+
}
69+
70+
function createContentHeader (isJSON) {
71+
return isJSON
72+
? {'Accept': 'application/json', 'Content-Type': 'application/json;charset=UTF-8'}
73+
: {}
74+
}
75+
76+
function createErrorResponse (res) {
77+
return res.headers
78+
? createResponse(res)
79+
: Promise.resolve(res)
80+
}
81+
82+
async function createResponse (res) {
83+
return deserialize(res).then((value) => ({
84+
url: res.url,
85+
status: res.status,
86+
statusText: res.statusText,
87+
headers: res.headers,
88+
value
89+
}), (err) => {
90+
return {
91+
value: err
92+
}
93+
})
94+
}
95+
96+
function deserialize (res) {
97+
const header = res.headers.get('Content-Type') || ''
98+
if (header.indexOf('application/json') > -1) return res.json()
99+
if (header.indexOf('application/ld+json') > -1) return res.json()
100+
if (header.indexOf('application/octet-stream') > -1) return res.arrayBuffer()
101+
return res.text()
102+
}

lib/react/html.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = function html ({
2+
title
3+
}) {
4+
return `
5+
<!DOCTYPE html>
6+
<html>
7+
<head>
8+
<meta charset="utf-8">
9+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
10+
<link href="assets/index.css" rel="stylesheet">
11+
12+
<title>${title}</title>
13+
</head>
14+
<body>
15+
<div id="root"></div>
16+
<script src="assets/index.js"></script>
17+
</body>
18+
</html>
19+
`
20+
}

lib/react/mount.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react'
2+
import {render} from 'react-dom'
3+
import {Provider} from 'react-redux'
4+
import {browserHistory} from 'react-router'
5+
import {syncHistoryWithStore} from 'react-router-redux'
6+
7+
if (process.env.NODE_ENV === 'development') {
8+
const Perf = window.Perf = require('react-addons-perf')
9+
Perf.start()
10+
}
11+
12+
export default function mount ({
13+
id,
14+
router,
15+
store
16+
}) {
17+
const history = syncHistoryWithStore(browserHistory, store)
18+
render(
19+
React.createElement(Provider, {store}, React.createElement(router, {history})),
20+
document.getElementById(id)
21+
)
22+
}

lib/react/store/index.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import merge from 'lodash.merge'
2+
import {combineReducers} from 'redux'
3+
import {routerReducer as routing} from 'react-router-redux'
4+
5+
let configureStore = null
6+
if (process.env.NODE_ENV === 'production') {
7+
configureStore = require('./store.production')
8+
} else {
9+
configureStore = require('./store.development')
10+
}
11+
12+
export default function createStore (reducers) {
13+
return configureStore(
14+
merge(
15+
safeParse(process.env.STORE),
16+
safeParse(window.localStorage ? window.localStorage.getItem('state') : {})
17+
),
18+
combineReducers({routing, ...reducers})
19+
)
20+
}
21+
22+
function safeParse (str) {
23+
try {
24+
return JSON.parse(str) || {}
25+
} catch (e) {
26+
return {}
27+
}
28+
}

lib/react/store/multi.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function multiMiddleware ({dispatch}) {
2+
return (next) => (action) => {
3+
return Array.isArray(action)
4+
? action.filter(Boolean).map(dispatch)
5+
: next(action)
6+
}
7+
}

lib/react/store/store.development.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {browserHistory} from 'react-router'
2+
import {routerMiddleware} from 'react-router-redux'
3+
import {applyMiddleware, createStore} from 'redux'
4+
import createLogger from 'redux-logger'
5+
import promise from 'redux-promise'
6+
7+
import {middleware as fetch} from '../fetch'
8+
import multi from './multi'
9+
10+
const logger = createLogger({
11+
collapsed: true,
12+
duration: true
13+
})
14+
15+
export default function configureStore (initialState, rootReducer) {
16+
return createStore(
17+
rootReducer,
18+
initialState,
19+
applyMiddleware(
20+
routerMiddleware(browserHistory),
21+
fetch,
22+
multi,
23+
promise,
24+
logger
25+
)
26+
)
27+
}

lib/react/store/store.production.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {browserHistory} from 'react-router'
2+
import {routerMiddleware} from 'react-router-redux'
3+
import {applyMiddleware, createStore} from 'redux'
4+
import promise from 'redux-promise'
5+
6+
import {middleware as fetch} from '../fetch'
7+
import multi from './multi'
8+
9+
export default function configureStore (initialState, rootReducer) {
10+
return createStore(
11+
rootReducer,
12+
initialState,
13+
applyMiddleware(
14+
routerMiddleware(browserHistory),
15+
fetch,
16+
multi,
17+
promise
18+
)
19+
)
20+
}

lib/util.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
const fs = require('fs')
2-
const path = require('path')
3-
42
const mkdirp = require('mkdirp')
3+
const path = require('path')
54

6-
const pkg = require('../lib/pkg')
5+
const pkg = require('./pkg')
76

87
exports.makeGetFn = (targets) => {
98
return (item) => {

0 commit comments

Comments
 (0)