Skip to content

Commit f137824

Browse files
authored
Version v1.1.1
* errors now dispatched for all query `.catch` methods * includes authProvider bug fix in #11 * Provider name validity checking (only Google, Twitter, Facebook, and Github) * Tests + Coverage improved with spies using `sinon` and `sinon-chai`
2 parents 6ff2727 + 7b50454 commit f137824

File tree

16 files changed

+442
-300
lines changed

16 files changed

+442
-300
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ View deployed version of Material Example [here](https://redux-firebasev3.fireba
2626
- queries support ( `orderByChild`, `orderByKey`, `orderByValue`, `orderByPriority`, `limitToLast`, `limitToFirst`, `startAt`, `endAt`, `equalTo` right now )
2727
- Automatic binding/unbinding
2828
- Declarative decorator syntax for React components
29-
- [`redux-thunk`](https://github.com/gaearon/redux-thunk) and [redux-observable](https://redux-observable.js.org/) integrations
30-
- Action Types and other Constants exported for external use (such as in redux-observable)
29+
- [`redux-thunk`](https://github.com/gaearon/redux-thunk) and [`redux-observable`](https://redux-observable.js.org/) integrations
30+
- Action Types and other Constants exported for external use (such as in `redux-observable`)
3131
- Firebase v3+ support
3232

3333
## Install

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-redux-firebase",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "Redux integration for Firebase. Comes with a Higher Order Component for use with React.",
55
"main": "dist/index.js",
66
"module": "src/index.js",
@@ -61,6 +61,7 @@
6161
"babel-preset-react": "^6.16.0",
6262
"babel-preset-stage-1": "^6.16.0",
6363
"chai": "^3.5.0",
64+
"chai-as-promised": "^6.0.0",
6465
"codecov": "^1.0.1",
6566
"eslint": "^3.10.2",
6667
"eslint-config-standard": "^6.2.1",
@@ -74,7 +75,9 @@
7475
"mocha": "^3.1.2",
7576
"react-addons-test-utils": "^15.4.0",
7677
"react-dom": "^15.4.0",
77-
"rimraf": "^2.5.4"
78+
"rimraf": "^2.5.4",
79+
"sinon": "^1.17.6",
80+
"sinon-chai": "^2.8.0"
7881
},
7982
"license": "MIT",
8083
"repository": {

src/actions/query.js

Lines changed: 34 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,13 @@
11
import { actionTypes } from '../constants'
22
import { promisesForPopulate } from '../utils/populate'
3-
import { getQueryIdFromPath, applyParamsToQuery } from '../utils/query'
3+
import {
4+
applyParamsToQuery,
5+
getWatcherCount,
6+
setWatcher,
7+
unsetWatcher
8+
} from '../utils/query'
49

5-
const { SET, NO_VALUE } = actionTypes
6-
7-
const getWatchPath = (event, path) =>
8-
`${event}:${((path.substring(0, 1) === '/') ? '' : '/')}${path}`
9-
10-
/**
11-
* @description Set a new watcher
12-
* @param {Object} firebase - Internal firebase object
13-
* @param {String} event - Type of event to watch for
14-
* @param {String} path - Path to watch with watcher
15-
* @param {String} queryId - Id of query
16-
*/
17-
const setWatcher = (firebase, event, path, queryId = undefined) => {
18-
const id = queryId ? `${event}:/${queryId}` : getWatchPath(event, path)
19-
20-
if (firebase._.watchers[id]) {
21-
firebase._.watchers[id]++
22-
} else {
23-
firebase._.watchers[id] = 1
24-
}
25-
26-
return firebase._.watchers[id]
27-
}
28-
29-
/**
30-
* @description Get count of currently attached watchers
31-
* @param {Object} firebase - Internal firebase object
32-
* @param {String} event - Type of event to watch for
33-
* @param {String} path - Path to watch with watcher
34-
* @param {String} queryId - Id of query
35-
*/
36-
const getWatcherCount = (firebase, event, path, queryId = undefined) => {
37-
const id = queryId ? `${event}:/${queryId}` : getWatchPath(event, path)
38-
return firebase._.watchers[id]
39-
}
40-
41-
/**
42-
* @description Remove/Unset a watcher
43-
* @param {Object} firebase - Internal firebase object
44-
* @param {String} event - Type of event to watch for
45-
* @param {String} path - Path to watch with watcher
46-
* @param {String} queryId - Id of query
47-
*/
48-
const unsetWatcher = (firebase, event, path, queryId = undefined) => {
49-
let id = queryId || getQueryIdFromPath(path)
50-
path = path.split('#')[0]
51-
52-
if (!id) {
53-
id = getWatchPath(event, path)
54-
}
55-
56-
if (firebase._.watchers[id] <= 1) {
57-
delete firebase._.watchers[id]
58-
if (event !== 'first_child') {
59-
firebase.database().ref().child(path).off(event)
60-
}
61-
} else if (firebase._.watchers[id]) {
62-
firebase._.watchers[id]--
63-
}
64-
}
10+
const { SET, NO_VALUE, ERROR } = actionTypes
6511

6612
/**
6713
* @description Watch a specific event type
@@ -102,6 +48,12 @@ export const watchEvent = (firebase, dispatch, { type, path, populates, queryPar
10248
path
10349
})
10450
}
51+
return snapshot
52+
}, (err) => {
53+
dispatch({
54+
type: ERROR,
55+
payload: err
56+
})
10557
})
10658
}
10759

@@ -115,16 +67,25 @@ export const watchEvent = (firebase, dispatch, { type, path, populates, queryPar
11567
// Handle once queries
11668
if (e === 'once') {
11769
return q.once('value')
118-
.then(snapshot =>
70+
.then(snapshot => {
71+
if (snapshot.val() !== null) {
72+
dispatch({
73+
type: SET,
74+
path,
75+
data: snapshot.val()
76+
})
77+
}
78+
return snapshot
79+
}, (err) => {
11980
dispatch({
120-
type: SET,
121-
path,
122-
data: snapshot.val()
81+
type: ERROR,
82+
payload: err
12383
})
124-
)
84+
})
12585
}
12686
// Handle all other queries
127-
// TODO: Handle onError of query (i.e. security errors)
87+
88+
/* istanbul ignore next: is run by tests but doesn't show in coverage */
12889
q.on(e, snapshot => {
12990
let data = (e === 'child_removed') ? undefined : snapshot.val()
13091
const resultPath = dest || (e === 'value') ? p : `${p}/${snapshot.key}`
@@ -156,10 +117,15 @@ export const watchEvent = (firebase, dispatch, { type, path, populates, queryPar
156117
data: list
157118
})
158119
})
120+
}, (err) => {
121+
dispatch({
122+
type: ERROR,
123+
payload: err
124+
})
159125
})
160126
}
161127

162-
runQuery(query, type, path, queryParams)
128+
return runQuery(query, type, path, queryParams)
163129
}
164130

165131
/**

src/constants.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,28 @@ export const actionTypes = {
2020
LOGIN_ERROR: `${prefix}LOGIN_ERROR`,
2121
NO_VALUE: `${prefix}NO_VALUE`,
2222
UNAUTHORIZED_ERROR: `${prefix}UNAUTHORIZED_ERROR`,
23+
ERROR: `${prefix}ERROR`,
2324
INIT_BY_PATH: `${prefix}INIT_BY_PATH`,
2425
AUTHENTICATION_INIT_STARTED: `${prefix}AUTHENTICATION_INIT_STARTED`,
2526
AUTHENTICATION_INIT_FINISHED: `${prefix}AUTHENTICATION_INIT_FINISHED`
2627
}
2728

29+
// List of all external auth providers that are supported (firebase's email/anonymous included by default)
30+
export const supportedAuthProviders = [
31+
'google',
32+
'github',
33+
'twitter',
34+
'facebook'
35+
]
36+
2837
export default {
2938
defaultJWTKeys,
30-
actionTypes
39+
actionTypes,
40+
supportedAuthProviders
3141
}
3242

3343
module.exports = {
3444
defaultJWTKeys,
35-
actionTypes
45+
actionTypes,
46+
supportedAuthProviders
3647
}

src/utils/auth.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { capitalize, isArray, isString } from 'lodash'
2-
2+
import { supportedAuthProviders } from '../constants'
3+
/**
4+
* @description Get correct login method and params order based on provided credentials
5+
* @param {Object} firebase - Internal firebase object
6+
* @param {String} providerName - Name of Auth Provider (i.e. google, github, facebook, twitter)
7+
* @param {Array|String} scopes - List of scopes to add to auth provider
8+
*/
39
export const createAuthProvider = (firebase, providerName, scopes) => {
4-
// TODO: Verify provider
510
// TODO: Verify scopes are valid before adding
11+
// Verify providerName is valid
12+
if (supportedAuthProviders.indexOf(providerName.toLowerCase()) === -1) {
13+
throw new Error(`${providerName} is not a valid Auth Provider`)
14+
}
615
const provider = new firebase.auth[`${capitalize(providerName)}AuthProvider`]()
716
provider.addScope('email')
817
if (scopes) {
@@ -20,12 +29,14 @@ export const createAuthProvider = (firebase, providerName, scopes) => {
2029

2130
/**
2231
* @description Get correct login method and params order based on provided credentials
32+
* @param {Object} firebase - Internal firebase object
2333
* @param {Object} credentials - Login credentials
2434
* @param {String} credentials.email - Email to login with (only needed for email login)
2535
* @param {String} credentials.password - Password to login with (only needed for email login)
2636
* @param {String} credentials.provider - Provider name such as google, twitter (only needed for 3rd party provider login)
2737
* @param {String} credentials.type - Popup or redirect (only needed for 3rd party provider login)
2838
* @param {String} credentials.token - Custom or provider token
39+
* @param {String} credentials.scopes - Scopes to add to provider (i.e. email)
2940
*/
3041
export const getLoginMethodAndParams = (firebase, {email, password, provider, type, token, scopes}) => {
3142
if (provider) {

src/utils/index.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { isFunction } from 'lodash'
2-
import { promisesForPopulate } from './populate'
32
import { getEventsFromInput } from './events'
43

54
/**
@@ -9,12 +8,10 @@ import { getEventsFromInput } from './events'
98
export const createCallable = f => isFunction(f) ? f : () => f
109

1110
export {
12-
promisesForPopulate,
1311
getEventsFromInput
1412
}
1513

1614
export default {
1715
createCallable,
18-
promisesForPopulate,
1916
getEventsFromInput
2017
}

src/utils/populate.js

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -57,49 +57,49 @@ export const getPopulateChild = (firebase, populate, id) =>
5757
* @param {Object} populateString - String containg population data
5858
*/
5959
export const promisesForPopulate = (firebase, originalData, populates) => {
60-
// TODO: Handle multiple populates
61-
// TODO: Handle single object based populates
6260
// TODO: Handle selecting of parameter to populate with (i.e. displayName of users/user)
63-
const firstPopulate = populates[0]
64-
// Loop over each object in list
6561
let promisesArray = []
66-
forEach(originalData, (d, key) => {
67-
// TODO: Handle input of [] within child (notating parameter for whole list)
68-
// Get value of parameter to be populated (key or list of keys)
69-
const idOrList = get(d, `${firstPopulate.child}`)
62+
// Loop over all populates
63+
forEach(populates, (p) =>
64+
// Loop over each object in list
65+
forEach(originalData, (d, key) => {
66+
// TODO: Handle input of [] within child (notating parameter for whole list)
67+
// Get value of parameter to be populated (key or list of keys)
68+
const idOrList = get(d, `${p.child}`)
7069

71-
// Parameter/child to be populated does not exist
72-
if (!idOrList) {
73-
return
74-
}
70+
// Parameter/child to be populated does not exist
71+
if (!idOrList) {
72+
return
73+
}
7574

76-
// Parameter is single ID
77-
if (isString(idOrList)) {
78-
return promisesArray.push(
79-
getPopulateChild(firebase, firstPopulate, idOrList)
80-
.then((v) =>
81-
// replace parameter with loaded object
82-
set(originalData, `${key}.${firstPopulate.child}`, v)
75+
// Parameter is single ID
76+
if (isString(idOrList)) {
77+
return promisesArray.push(
78+
getPopulateChild(firebase, p, idOrList)
79+
.then((v) =>
80+
// replace parameter with loaded object
81+
set(originalData, `${key}.${p.child}`, v)
82+
)
8383
)
84-
)
85-
}
84+
}
8685

87-
// Parameter is a list of ids
88-
if (isArray(idOrList)) {
89-
// Create single promise that includes a promise for each child
90-
return promisesArray.push(
91-
Promise.all(
92-
map(idOrList, (id) =>
93-
getPopulateChild(firebase, firstPopulate, id)
86+
// Parameter is a list of ids
87+
if (isArray(idOrList)) {
88+
// Create single promise that includes a promise for each child
89+
return promisesArray.push(
90+
Promise.all(
91+
map(idOrList, (id) =>
92+
getPopulateChild(firebase, p, id)
93+
)
94+
)
95+
// replace parameter with populated list
96+
.then((v) =>
97+
set(originalData, `${key}.${p.child}`, v)
9498
)
9599
)
96-
// replace parameter with populated list
97-
.then((v) =>
98-
set(originalData, `${key}.${firstPopulate.child}`, v)
99-
)
100-
)
101-
}
102-
})
100+
}
101+
})
102+
)
103103

104104
// Return original data after population promises run
105105
return Promise.all(promisesArray).then(d => originalData)

0 commit comments

Comments
 (0)