diff --git a/.eslintrc b/.eslintrc index 9d9934c0c..a88694083 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,24 +1,27 @@ -{ - "parser" : "babel-eslint", - "extends" : [ - "standard", - "standard-react" - ], - "plugins": [ - "babel" - ], - "env" : { - "browser" : true - }, - "globals" : { - "__DEV__" : false, - "__PROD__" : false, - "__DEBUG__" : false, - "__COVERAGE__" : false, - "__BASENAME__" : false - }, - "rules": { - "semi" : [2, "never"], - "no-console": "error" - } -} +root: true + +parser: babel-eslint + +extends: [standard, standard-react] +plugins: [babel, react] + +env: + browser: true + es6: true + jasmine: true + node: true + +ecmaFeatures: + jsx: true + modules: true + +globals: + MockFirebase: true + sinon: true + Raven: true + __COVERAGE__: true + __DEV__: true + +rules: + semi: [2, 'never'] + no-console: 'error' diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..2239b0ef6 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +docs +bin +tests +examples +coverage +_book diff --git a/README.md b/README.md index be3565105..b470f7a58 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Code Style][code-style-image]][code-style-url] [![Gitter][gitter-image]][gitter-url] -[](http://packagequality.com/#?package=react-redux-firebase) +[](https://packagequality.com/#?package=react-redux-firebase) > Redux bindings for Firebase. Includes Higher Order Component (HOC) for use with React. @@ -110,7 +110,8 @@ const store = createStoreWithFirebase(rootReducer, initialState) In components: ```javascript -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { connect } from 'react-redux' import { firebaseConnect, diff --git a/SUMMARY.md b/SUMMARY.md index 354459f7b..6ade96a3f 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -16,6 +16,7 @@ * [Populate](/docs/recipes/populate.md) * [Upload](/docs/recipes/upload.md) * [Redux Form](/docs/recipes/redux-form.md) + * [Redux Persist](/docs/recipes/redux-persist.md) * [React Native](/docs/recipes/react-native.md) * [Server Side Rendering](/docs/recipes/ssr.md) * [API Reference](/docs/api/README.md) diff --git a/book.json b/book.json index 357293a8b..c9f9b1ffd 100644 --- a/book.json +++ b/book.json @@ -1,7 +1,7 @@ { "gitbook": ">=3.2.1", "title": "React Redux Firebase", - "plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs"], + "plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs", "versions"], "pluginsConfig": { "edit-link": { "base": "https://github.com/prescottprue/react-redux-firebase/tree/master", @@ -14,6 +14,20 @@ "styles": { "website": "build/gitbook.css" } + }, + "versions": { + "gitbookConfigURL": "https://raw.githubusercontent.com/prescottprue/react-redux-firebase/master/book.json", + "options": [ + { + "value": "https://storage.googleapis.com/react-redux-firebase-13cdb.appspot.com/docs/history/v1.4.0/index.html", + "text": "Version 1.4.0", + "selected": true + }, + { + "value": "https://storage.googleapis.com/react-redux-firebase-13cdb.appspot.com/docs/history/v2.0.0/index.html", + "text": "Version 2.0.0" + } + ] } } } diff --git a/docs/api/compose.md b/docs/api/compose.md index df9d9c747..ecdd560ee 100644 --- a/docs/api/compose.md +++ b/docs/api/compose.md @@ -40,11 +40,17 @@ Middleware that handles configuration (placed in redux's auth redirect handling listener. (default: `true`) - `config.onAuthStateChanged` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Function run when auth state changes. Argument Pattern: `(authData, firebase, dispatch)` + - `config.enableEmptyAuthChanges` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether or not to enable + empty auth changes. When set to true, `onAuthStateChanged` will be fired with, + empty auth changes such as undefined on initialization. See + [#137](https://github.com/prescottprue/react-redux-firebase/issues/137) for + more details. (default: `false`) - `config.onRedirectResult` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Function run when redirect result is returned. Argument Pattern: `(authData, firebase, dispatch)` - `config.customAuthParameters` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object for setting which customAuthParameters are passed to external auth providers. - - `config.profileFactory` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Factory for modifying how user profile is saved. + - `config.profileFactory` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Factory for modifying how user + profile is saved. - `config.uploadFileDataFactory` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Factory for modifying how file meta data is written during file uploads - `config.profileParamsToPopulate` **([Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Parameters within diff --git a/docs/api/constants.md b/docs/api/constants.md index 3b9568b23..b21924463 100644 --- a/docs/api/constants.md +++ b/docs/api/constants.md @@ -1,16 +1,7 @@ - - -### Table of Contents - -- [actionsPrefix](#actionsprefix) -- [actionTypes](#actiontypes) - -## actionsPrefix +# actionsPrefix Prefix for all actions within library -Type: [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) - **Examples** ```javascript @@ -18,7 +9,7 @@ import { constants } from 'react-redux-firebase' constants.actionsPrefix === '@@reactReduxFirebase' // true ``` -## actionTypes +# actionTypes Object containing all action types @@ -26,6 +17,7 @@ Object containing all action types - `START` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/START` - `SET` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/SET` +- `SET_ORDERED` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/SET_ORDERED` - `SET_PROFILE` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/SET_PROFILE` - `LOGIN` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/LOGIN` - `LOGOUT` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/LOGOUT` @@ -42,6 +34,15 @@ Object containing all action types - `FILE_DELETE_START` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/FILE_DELETE_START` - `FILE_DELETE_ERROR` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/FILE_DELETE_ERROR` - `FILE_DELETE_COMPLETE` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/FILE_DELETE_COMPLETE` +- `AUTH_UPDATE_START` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/AUTH_UPDATE_START` +- `AUTH_UPDATE_ERROR` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/AUTH_UPDATE_ERROR` +- `AUTH_UPDATE_COMPLETE` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/AUTH_UPDATE_COMPLETE` +- `PROFILE_UPDATE_START` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/PROFILE_UPDATE_START` +- `PROFILE_UPDATE_ERROR` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/PROFILE_UPDATE_ERROR` +- `PROFILE_UPDATE_COMPLETE` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/PROFILE_UPDATE_COMPLETE` +- `EMAIL_UPDATE_START` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/EMAIL_UPDATE_START` +- `EMAIL_UPDATE_ERROR` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/EMAIL_UPDATE_ERROR` +- `EMAIL_UPDATE_COMPLETE` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** `@@reactReduxFirebase/EMAIL_UPDATE_COMPLETE` **Examples** @@ -50,7 +51,7 @@ import { actionTypes } from 'react-redux-firebase' actionTypes.SET === '@@reactReduxFirebase/SET' // true ``` -## +# defaultConfig Default configuration options @@ -65,6 +66,11 @@ Default configuration options - `enableRedirectHandling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `true` Whether or not to enable redirect handling. This must be disabled if environment is not http/https such as with react-native. +- `enableEmptyAuthChanges` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `false` Whether or not to enable + empty auth changes. When set to true, `onAuthStateChanged` will be fired with, + empty auth changes such as `undefined` on initialization + (see [#137](https://github.com/prescottprue/react-redux-firebase/issues/137)). + Requires `v1.5.0-alpha` or higher. - `autoPopulateProfile` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `true` Whether or not to automatically populate profile with data loaded through profileParamsToPopulate config. @@ -73,7 +79,7 @@ Default configuration options the data path. For example: role paramter on profile populated from 'roles' root. True will call SET_PROFILE as well as a SET action with the role that is loaded (places it in data/roles). -- `distpatchOnUnsetListener` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `false` Whether or not to +- `dispatchOnUnsetListener` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** `false` Whether or not to dispatch UNSET_LISTENER when disabling listeners for a specific path. USE WITH CAUTION Setting this to true allows an action to be called that removes data from redux (which might not always be expected). diff --git a/docs/api/helpers.md b/docs/api/helpers.md index 82e97cb7d..d0a531983 100644 --- a/docs/api/helpers.md +++ b/docs/api/helpers.md @@ -1,16 +1,4 @@ - - -### Table of Contents - -- [isLoaded](#isloaded) -- [isEmpty](#isempty) -- [toJS](#tojs) -- [pathToJS](#pathtojs) -- [dataToJS](#datatojs) -- [orderedToJS](#orderedtojs) -- [customToJS](#customtojs) - -## isLoaded +# isLoaded Detect whether items are loaded yet or not @@ -21,127 +9,35 @@ Detect whether items are loaded yet or not **Examples** ```javascript -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { connect } from 'react-redux' import { firebaseConnect, isLoaded, dataToJS } from 'react-redux-firebase' ``` Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether or not item is loaded -## isEmpty +# isEmpty Detect whether items are empty or not **Parameters** -- `data` - `item` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Item to check loaded status of. A comma seperated list is also acceptable. +- `data` **Examples** ```javascript -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { connect } from 'react-redux' import { firebaseConnect, isEmpty, dataToJS } from 'react-redux-firebase' ``` Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether or not item is empty -## toJS - -Convert Immutable Map to a Javascript object - -**Parameters** - -- `data` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Immutable Map to be converted to JS object (state.firebase) - -Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** data - Javascript version of Immutable Map - -Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Data located at path within Immutable Map - -## pathToJS - -Convert parameter from Immutable Map to a Javascript object - -**Parameters** - -- `data` -- `path` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Path from state.firebase to convert to JS object -- `notSetValue` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Value to use if data is not available -- `firebase` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** Immutable Map to be converted to JS object (state.firebase) - -**Examples** - -_Basic_ - -```javascript -import { connect } from 'react-redux' -import { firebaseConnect, pathToJS } from 'react-redux-firebase' -``` - -Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Data located at path within Immutable Map - -## dataToJS - -Convert parameter under "data" path of Immutable Map to a Javascript object. -**NOTE:** Setting a default value will cause `isLoaded` to always return true - -**Parameters** - -- `data` -- `path` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Path of parameter to load -- `notSetValue` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Value to return if value is not - found in redux. This will cause `isLoaded` to always return true (since - value is set from the start). -- `firebase` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** Immutable Map to be converted to JS object (state.firebase) - -**Examples** - -_Basic_ - -```javascript -import { connect } from 'react-redux' -import { firebaseConnect, dataToJS } from 'react-redux-firebase' -``` - -_Default Value_ - -```javascript -import { connect } from 'react-redux' -import { firebaseConnect, dataToJS } from 'react-redux-firebase' -const defaultValue = { - 1: { - text: 'Example Todo' - } -} -``` - -Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Data located at path within Immutable Map - -## orderedToJS - -Convert parameter under "ordered" path of Immutable Map to a -Javascript array. This preserves order set by query. - -**Parameters** - -- `data` -- `path` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Path of parameter to load -- `notSetValue` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Value to return if value is not found -- `firebase` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** Immutable Map to be converted to JS object (state.firebase) - -**Examples** - -_Basic_ - -```javascript -import { connect } from 'react-redux' -import { firebaseConnect, orderedToJS } from 'react-redux-firebase' -``` - -Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Data located at path within Immutable Map - -## +# helpers Convert parameter under "data" path of Immutable Map to a Javascript object with parameters populated based on populates array @@ -176,35 +72,3 @@ export default connect(({ firebase }) => ({ ``` Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Data located at path within Immutable Map - -## customToJS - -Load custom object from within store - -**Parameters** - -- `data` -- `path` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Path of parameter to load -- `custom` -- `notSetValue` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Value to return if value is not found -- `firebase` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** Immutable Map to be converted to JS object (state.firebase) -- `customPath` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Part of store from which to load - -**Examples** - -_Basic_ - -```javascript -import { connect } from 'react-redux' -import { firebaseConnect, helpers } from 'react-redux-firebase' -const { customToJS } = helpers - -const fbWrapped = firebaseConnect(['/todos'])(App) - -export default connect(({ firebase }) => ({ - // this.props.todos loaded from state.firebase.data.todos - requesting: customToJS(firebase, 'todos', 'requesting') -}))(fbWrapped) -``` - -Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Data located at path within state diff --git a/docs/api/reducer.md b/docs/api/reducer.md index 8e4330f17..130ba3a3c 100644 --- a/docs/api/reducer.md +++ b/docs/api/reducer.md @@ -1,10 +1,75 @@ - +# requestingReducer -### Table of Contents +Reducer for requesting state. Changed by `START` and `SET` actions. -- [firebaseStateReducer](#firebasestatereducer) +**Parameters** + +- `state` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)](default {})** Current requesting redux state +- `action` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object containing the action that was dispatched + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Profile state after reduction + +# dataReducer + +Reducer for data state. Changed by `LOGIN`, `LOGOUT`, and `LOGIN_ERROR` +actions. + +**Parameters** + +- `state` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)](default {})** Current data redux state +- `action` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object containing the action that was dispatched + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Profile state after reduction + +# authReducer + +Reducer for auth state. Changed by `LOGIN`, `LOGOUT`, and `LOGIN_ERROR` +actions. + +**Parameters** + +- `state` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)](default {})** Current auth redux state +- `action` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object containing the action that was dispatched + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Profile state after reduction + +# profileReducer + +Reducer for profile state. Changed by `SET_PROFILE`, `LOGOUT`, and +`LOGIN_ERROR` actions. + +**Parameters** + +- `state` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)](default {})** Current profile redux state +- `action` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object containing the action that was dispatched + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Profile state after reduction + +# isInitializingReducer + +Reducer for isInitializing state. Changed by `AUTHENTICATION_INIT_STARTED` +and `AUTHENTICATION_INIT_FINISHED` actions. + +**Parameters** + +- `state` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)](default false)** Current isInitializing redux state +- `action` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object containing the action that was dispatched + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Profile state after reduction + +# errorsReducer + +Reducer for errors state. Changed by `UNAUTHORIZED_ERROR` +and `LOGOUT` actions. + +**Parameters** + +- `state` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)](default \[])** Current authError redux state +- `action` **[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object containing the action that was dispatched + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Profile state after reduction -## firebaseStateReducer +# firebaseStateReducer Reducer for react redux firebase. This function is called automatically by redux every time an action is fired. Based on which action @@ -13,9 +78,9 @@ changes. **Parameters** -- `state` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** Current Redux State (optional, default `initialState`) -- `action` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Action which will modify state (optional, default `{}`) +- `state` **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** Current Redux State +- `action` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Action which will modify state - `action.type` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Type of Action being called - `action.data` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Type of Action which will modify state -Returns **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** Redux State. +Returns **[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** State diff --git a/docs/recipes/auth.md b/docs/recipes/auth.md index e1876c911..7e357b3bf 100644 --- a/docs/recipes/auth.md +++ b/docs/recipes/auth.md @@ -5,7 +5,8 @@ Here is an example of a component that shows a Google login button if the user is not logged in, and a welcome message if they are. The initial loading state is handled with a simple "loading" message ```js -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import GoogleButton from 'react-google-button' import { connect } from 'react-redux' import { diff --git a/docs/recipes/redux-persist.md b/docs/recipes/redux-persist.md new file mode 100644 index 000000000..1a91501a9 --- /dev/null +++ b/docs/recipes/redux-persist.md @@ -0,0 +1,59 @@ +# Redux Persist + +In order to use `redux-persist`, you must use `redux-persist-transform-immutable` to transform state since `react-redux-firebase` uses immutable. + +**Note**: The immutable transform will no longer be necessary in `v2.0.0` as immutable will no longer be used. There are some small known issues with `v1.*.*`. Please share your feelings and input on [gitter](https://gitter.im/redux-firebase/Lobby). + +```js +import { applyMiddleware, compose, createStore } from 'redux' +import { browserHistory } from 'react-router' +import { reactReduxFirebase } from 'react-redux-firebase' +import { persistStore, autoRehydrate } from 'redux-persist' +import immutableTransform from 'redux-persist-transform-immutable' +import { firebase as fbConfig, reduxFirebase as reduxConfig } from '../config' +import makeRootReducer from './reducers' +import { updateLocation } from './location' + +export default (initialState = {}, history) => { + // ====================================================== + // Middleware Configuration + // ====================================================== + const middleware = [] + + // ====================================================== + // Store Enhancers + // ====================================================== + const enhancers = [] + if (__DEV__) { + const devToolsExtension = window.devToolsExtension + if (typeof devToolsExtension === 'function') { + enhancers.push(devToolsExtension()) + } + } + + // ====================================================== + // Store Instantiation + // ====================================================== + const store = createStore( + makeRootReducer(), + initialState, + compose( + applyMiddleware(...middleware), + reactReduxFirebase(fbConfig, reduxConfig), + autoRehydrate(), + ...enhancers + ) + ) + + // To unsubscribe, invoke `store.unsubscribeHistory()` anytime + store.unsubscribeHistory = browserHistory.listen(updateLocation(store)) + + // begin periodically persisting the store with a transform for the immutable state + persistStore(store, { + transforms: [immutableTransform()] + }) + + return store +} + +``` diff --git a/docs/roadmap.md b/docs/roadmap.md index 386b1cd16..1f5767148 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -41,8 +41,13 @@ * Use `storeAs` with populates - [#130](https://github.com/prescottprue/react-redux-firebase/issues/130) * `updateUser` method for updating currently authenticated user's user object (`/users/${uid}`) * `updateAuth` method for updating currently authenticated user's auth object [as seen in the Firebase docs](https://firebase.google.com/docs/auth/web/manage-users#get_a_users_provider-specific_profile_information) - [#129](https://github.com/prescottprue/react-redux-firebase/issues/129) -* Expose Firebase messaging -* Setting that allows for `waitForPopulate` to be turned off (i.e. return populated data as in becomes available). As of `v1.4.0-rc.2`, populate only sets `isLoaded` to true after all children are loaded, `waitForPopulate` would make this optional - [#121](https://github.com/prescottprue/react-redux-firebase/issues/121) +* Expose Firebase messaging (`firebase.messaging()`) +* Typescript typings - [#142](https://github.com/prescottprue/react-redux-firebase/issues/142) +* `enableEmptyAuthChanges` config option added - [#137](https://github.com/prescottprue/react-redux-firebase/issues/137) + +#### Enhancements/Fixes +* Return correct promise from `firebase.auth().signOut()` - [#152](https://github.com/prescottprue/react-redux-firebase/issues/152) +* Removed `browser` field from `package.json` so that webpack will point to `main` field - [#128](https://github.com/prescottprue/react-redux-firebase/issues/128) ## Future Minor Versions (`v1.6.0 - v1.*.*`) @@ -53,14 +58,26 @@ #### Features * Config option for populated items updating when changed - [#69](https://github.com/prescottprue/react-redux-firebase/issues/69) +* Expose whole Firebase instance (warning: Using Firebase instance methods will not dispatch actions or update redux state) * Config option to not remove all data on logout (potential config syntax: `preserveOnLogout: ['todos']`) -* Integration for [`react-native-firebase`](https://github.com/invertase/react-native-firebase) for using Firebase native modules instead of JS library +* Integration for [`react-native-firebase`](https://github.com/invertase/react-native-firebase) for using Firebase native modules instead of JS library (allowing for instance to be passed in). +* Setting that allows for `waitForPopulate` to be turned off (i.e. return populated data as in becomes available). As of `v1.4.0-rc.2`, populate only sets `isLoaded` to true after all children are loaded, `waitForPopulate` would make this optional - [#121](https://github.com/prescottprue/react-redux-firebase/issues/121) * Integration for [`react-native-google-signin`](https://github.com/devfd/react-native-google-signin) to simplify react-native authentication implementation * Nested populates - [#85](https://github.com/prescottprue/react-redux-firebase/issues/85) #### Enhancements/Fixes *None Yet Planned* +## Future Minor Versions (`v1.*.*`) + +**Note:** Subject to change + +#### Breaking Changes + *None Yet Planned* + +#### Features +* Nested populates [#85](https://github.com/prescottprue/react-redux-firebase/issues/85)) + ## Upcoming Major Version (`v2.0.0`) **NOTE:** The changes are unconfirmed and will most likely change @@ -83,6 +100,9 @@ #### Enhancements/Fixes * Implement [`firebase-server`](https://github.com/urish/firebase-server) for tests instead of using demo firebase instance +#### Enhancements +* Implement [`firebase-server`](https://github.com/urish/firebase-server) for tests instead of using demo firebase instance + ### Long Term Goals * Optional Built in Role Management * Multi-level population diff --git a/examples/complete/material/package.json b/examples/complete/material/package.json index 7ffeb4d3f..b00c5a65c 100644 --- a/examples/complete/material/package.json +++ b/examples/complete/material/package.json @@ -62,43 +62,38 @@ "lodash": "^4.17.2", "material-ui": "0.16.0", "normalize.css": "^4.1.1", + "prop-types": "^15.5.8", "react": "15.3.2", "react-dom": "15.3.2", "react-google-button": "^0.1.0", "react-redux": "^4.4.5", - "react-redux-firebase": "^1.4.0-rc.1", + "react-redux-firebase": "*", "react-router": "^2.8.0", "react-tap-event-plugin": "1.0.0", "redux": "^3.6.0", "redux-auth-wrapper": "^1.0.0", "redux-form": "^6.6.1", + "redux-logger": "^3.0.6", "redux-thunk": "^2.2.0" }, "devDependencies": { "babel-core": "^6.22.1", "babel-eslint": "^6.0.4", "babel-loader": "^6.2.5", - "babel-preset-es2015": "^6.14.0", - "babel-plugin-lodash": "^3.2.10", - "babel-preset-react": "^6.11.1", - "babel-preset-stage-0": "^6.3.13", "babel-plugin-istanbul": "^2.0.1", - "babel-plugin-transform-runtime": "^6.15.0", + "babel-plugin-lodash": "^3.2.10", "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-runtime": "^6.15.0", "babel-polyfill": "^6.16.0", + "babel-preset-es2015": "^6.14.0", + "babel-preset-react": "^6.11.1", + "babel-preset-stage-0": "^6.3.13", "babel-runtime": "^6.11.6", "better-npm-run": "^0.0.13", "connect-history-api-fallback": "^1.3.0", - "cssnano": "^3.7.4", "css-loader": "^0.25.0", + "cssnano": "^3.7.4", "debug": "^2.2.0", - "extract-text-webpack-plugin": "^1.0.0", - "file-loader": "^0.9.0", - "fs-extra": "^1.0.0", - "html-webpack-plugin": "^2.22.0", - "imports-loader": "^0.6.5", - "ip": "^1.1.2", - "json-loader": "^0.5.4", "enzyme": "^2.5.1", "eslint": "^3.9.1", "eslint-config-standard": "^6.2.1", @@ -108,18 +103,26 @@ "eslint-plugin-react": "^6.0.0", "eslint-plugin-standard": "^2.0.0", "express": "^4.14.0", + "extract-text-webpack-plugin": "^1.0.0", + "file-loader": "^0.9.0", + "fs-extra": "^1.0.0", + "html-webpack-plugin": "^2.22.0", + "imports-loader": "^0.6.5", + "ip": "^1.1.2", + "json-loader": "^0.5.4", "mocha": "^3.0.1", - "nodemon": "^1.10.2", "node-sass": "^3.7.0", + "nodemon": "^1.10.2", "postcss-loader": "^0.13.0", "redbox-react": "^1.2.10", + "redux-logger": "^3.0.6", "rimraf": "^2.5.4", "sass-loader": "^4.0.0", "style-loader": "^0.13.1", "url-loader": "^0.5.6", + "webpack": "^1.12.14", "webpack-dev-middleware": "1.10.1", "webpack-hot-middleware": "^2.12.2", - "webpack": "^1.12.14", "yargs": "^6.3.0" } } diff --git a/examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.js b/examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.js index 04ea86f55..494934d9b 100644 --- a/examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.js +++ b/examples/complete/material/src/components/LoadingSpinner/LoadingSpinner.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import CircularProgress from 'material-ui/CircularProgress' import classes from './LoadingSpinner.scss' diff --git a/examples/complete/material/src/components/TextField/TextField.js b/examples/complete/material/src/components/TextField/TextField.js index a29d79c62..e27b19841 100644 --- a/examples/complete/material/src/components/TextField/TextField.js +++ b/examples/complete/material/src/components/TextField/TextField.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import MaterialTextField from 'material-ui/TextField' export const TextField = ({ input, label, meta: { touched, error }, ...custom }) => ( diff --git a/examples/complete/material/src/containers/App/App.js b/examples/complete/material/src/containers/App/App.js index bdd848ad8..f2e3d83af 100644 --- a/examples/complete/material/src/containers/App/App.js +++ b/examples/complete/material/src/containers/App/App.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { browserHistory, Router } from 'react-router' import { Provider } from 'react-redux' diff --git a/examples/complete/material/src/containers/Navbar/Navbar.js b/examples/complete/material/src/containers/Navbar/Navbar.js index 0a91cb9cd..fe7d4e1c8 100644 --- a/examples/complete/material/src/containers/Navbar/Navbar.js +++ b/examples/complete/material/src/containers/Navbar/Navbar.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import classes from './Navbar.scss' import { Link } from 'react-router' import { connect } from 'react-redux' @@ -39,10 +40,9 @@ const avatarStyles = { @firebaseConnect() @connect( - ({ firebase }) => ({ - authError: pathToJS(firebase, 'authError'), - auth: pathToJS(firebase, 'auth'), - account: pathToJS(firebase, 'profile') + ({ firebase: { auth, profile } }) => ({ + auth, + profile, }) ) export default class Navbar extends Component { @@ -51,7 +51,7 @@ export default class Navbar extends Component { } static propTypes = { - account: PropTypes.object, + profile: PropTypes.object, firebase: PropTypes.object.isRequired } @@ -61,20 +61,20 @@ export default class Navbar extends Component { } render () { - const { account } = this.props - const accountExists = isLoaded(account) && !isEmpty(account) + const { profile } = this.props + const profileExists = isLoaded(profile) && !isEmpty(profile) const iconButton = (
- { accountExists && account.displayName ? account.displayName : 'User' } + { profileExists && profile.displayName ? profile.displayName : 'User' }
@@ -99,7 +99,7 @@ export default class Navbar extends Component {
) - const rightMenu = accountExists ? ( + const rightMenu = profileExists ? ( + material example } showMenuIconButton={false} iconElementRight={rightMenu} - iconStyleRight={accountExists ? avatarStyles.wrapper : {}} + iconStyleRight={profileExists ? avatarStyles.wrapper : {}} className={classes.appBar} /> ) diff --git a/examples/complete/material/src/routes/Account/components/AccountForm/AccountForm.js b/examples/complete/material/src/routes/Account/components/AccountForm/AccountForm.js index 45c7d28fe..4e1dd7d83 100644 --- a/examples/complete/material/src/routes/Account/components/AccountForm/AccountForm.js +++ b/examples/complete/material/src/routes/Account/components/AccountForm/AccountForm.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import { Field, reduxForm } from 'redux-form' import RaisedButton from 'material-ui/RaisedButton' import TextField from 'components/TextField' diff --git a/examples/complete/material/src/routes/Account/components/ProviderDataForm/ProviderDataForm.js b/examples/complete/material/src/routes/Account/components/ProviderDataForm/ProviderDataForm.js index f06be1f0f..fbf906afc 100644 --- a/examples/complete/material/src/routes/Account/components/ProviderDataForm/ProviderDataForm.js +++ b/examples/complete/material/src/routes/Account/components/ProviderDataForm/ProviderDataForm.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import { List, ListItem } from 'material-ui/List' import classes from './ProviderDataForm.scss' import AccountCircle from 'material-ui/svg-icons/action/account-circle' diff --git a/examples/complete/material/src/routes/Account/containers/AccountContainer.js b/examples/complete/material/src/routes/Account/containers/AccountContainer.js index a79dc3eff..dd08a8732 100644 --- a/examples/complete/material/src/routes/Account/containers/AccountContainer.js +++ b/examples/complete/material/src/routes/Account/containers/AccountContainer.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import Paper from 'material-ui/Paper' import { connect } from 'react-redux' import { firebaseConnect, pathToJS, isLoaded } from 'react-redux-firebase' @@ -12,14 +13,14 @@ import classes from './AccountContainer.scss' @UserIsAuthenticated // redirect to /login if user is not authenticated @firebaseConnect() // add this.props.firebase @connect( // Map redux state to props - ({ firebase }) => ({ - auth: pathToJS(firebase, 'auth'), - account: pathToJS(firebase, 'profile') + ({ firebase: { auth, profile } }) => ({ + auth, + profile }) ) export default class Account extends Component { static propTypes = { - account: PropTypes.object, + profile: PropTypes.object, auth: PropTypes.shape({ uid: PropTypes.string }), @@ -41,16 +42,16 @@ export default class Account extends Component { updateAccount = (newData) => this.props.firebase - .update(`${rfConfig.userProfile}/${this.props.auth.uid}`, newData) + .updateProfile(newData) .catch((err) => { console.error('Error updating account', err) // eslint-disable-line no-console // TODO: Display error to user }) render () { - const { account } = this.props + const { profile } = this.props - if (!isLoaded(account)) { + if (!isLoaded(profile)) { return } @@ -61,14 +62,14 @@ export default class Account extends Component {
diff --git a/examples/complete/material/src/routes/Home/components/NewTodoPanel/NewTodoPanel.js b/examples/complete/material/src/routes/Home/components/NewTodoPanel/NewTodoPanel.js index 3ef8702c3..98b383757 100755 --- a/examples/complete/material/src/routes/Home/components/NewTodoPanel/NewTodoPanel.js +++ b/examples/complete/material/src/routes/Home/components/NewTodoPanel/NewTodoPanel.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import IconButton from 'material-ui/IconButton' import Paper from 'material-ui/Paper' import TextField from 'material-ui/TextField' diff --git a/examples/complete/material/src/routes/Home/components/TodoItem/TodoItem.js b/examples/complete/material/src/routes/Home/components/TodoItem/TodoItem.js index f45c9581c..2c427010b 100755 --- a/examples/complete/material/src/routes/Home/components/TodoItem/TodoItem.js +++ b/examples/complete/material/src/routes/Home/components/TodoItem/TodoItem.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import classes from './TodoItem.scss' import { ListItem } from 'material-ui/List' import Checkbox from 'material-ui/Checkbox' diff --git a/examples/complete/material/src/routes/Home/containers/HomeContainer.js b/examples/complete/material/src/routes/Home/containers/HomeContainer.js index 6f718b1ac..bfac5190d 100755 --- a/examples/complete/material/src/routes/Home/containers/HomeContainer.js +++ b/examples/complete/material/src/routes/Home/containers/HomeContainer.js @@ -1,14 +1,12 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { connect } from 'react-redux' import { map } from 'lodash' import Theme from 'theme' import { firebaseConnect, isLoaded, - pathToJS, - dataToJS // needed for full list and once - // orderedToJS // needed for ordered list - // populatedDataToJS // needed for populated list + populate // for populated list } from 'react-redux-firebase' import CircularProgress from 'material-ui/CircularProgress' import Snackbar from 'material-ui/Snackbar' @@ -19,21 +17,22 @@ import TodoItem from '../components/TodoItem' import NewTodoPanel from '../components/NewTodoPanel' import classes from './HomeContainer.scss' -// const populates = [{ child: 'owner', root: 'users', keyProp: 'uid' }] +const populates = [{ child: 'owner', root: 'users' }] @firebaseConnect([ // 'todos' // sync full list of todos // { path: 'todos', type: 'once' } // for loading once instead of binding - { path: 'todos', queryParams: ['orderByKey', 'limitToLast=5'] } // 10 most recent - // { path: 'todos', populates } // populate + // { path: 'todos', queryParams: ['orderByKey', 'limitToLast=5'] } // 10 most recent + { path: 'todos', populates } // populate ]) @connect( - ({firebase}) => ({ - auth: pathToJS(firebase, 'auth'), - account: pathToJS(firebase, 'profile'), - todos: dataToJS(firebase, 'todos') - // todos: populatedDataToJS(firebase, '/todos', populates), // if populating - // todos: orderedToJS(firebase, '/todos') // if using ordering such as orderByChild + // get auth, profile, and data from + ({ firebase, firebase: { auth, profile, data: { todos } } }) => ({ + auth, + profile, + // todos, + todos: populate(firebase, 'todos', populates), // if populating + // todos: firebase.ordered.todos // if using ordering such as orderByChild }) ) export default class Home extends Component { @@ -102,7 +101,7 @@ export default class Home extends Component { render () { const { todos } = this.props const { error } = this.state - + console.log('todos: ', todos) return (
{ diff --git a/examples/complete/material/src/routes/Login/components/LoginForm/LoginForm.js b/examples/complete/material/src/routes/Login/components/LoginForm/LoginForm.js index c0aac901c..b331d5b5f 100644 --- a/examples/complete/material/src/routes/Login/components/LoginForm/LoginForm.js +++ b/examples/complete/material/src/routes/Login/components/LoginForm/LoginForm.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import { Link } from 'react-router' import { Field, reduxForm } from 'redux-form' import RaisedButton from 'material-ui/RaisedButton' diff --git a/examples/complete/material/src/routes/Login/containers/LoginContainer.js b/examples/complete/material/src/routes/Login/containers/LoginContainer.js index 1a6ff6e5c..3d29df527 100644 --- a/examples/complete/material/src/routes/Login/containers/LoginContainer.js +++ b/examples/complete/material/src/routes/Login/containers/LoginContainer.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { Link } from 'react-router' import GoogleButton from 'react-google-button' import { connect } from 'react-redux' diff --git a/examples/complete/material/src/routes/Projects/components/NewProjectDialog/NewProjectDialog.js b/examples/complete/material/src/routes/Projects/components/NewProjectDialog/NewProjectDialog.js index 8eed2d23a..42ae6d33d 100644 --- a/examples/complete/material/src/routes/Projects/components/NewProjectDialog/NewProjectDialog.js +++ b/examples/complete/material/src/routes/Projects/components/NewProjectDialog/NewProjectDialog.js @@ -1,5 +1,6 @@ -import React, { Component, PropTypes } from 'react' -import { Field, reduxForm } from 'redux-form' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { reduxForm } from 'redux-form' import Dialog from 'material-ui/Dialog' import FlatButton from 'material-ui/FlatButton' import TextField from 'components/TextField' diff --git a/examples/complete/material/src/routes/Projects/components/NewProjectTile/NewProjectTile.js b/examples/complete/material/src/routes/Projects/components/NewProjectTile/NewProjectTile.js index 006996ba5..95a8df5a1 100644 --- a/examples/complete/material/src/routes/Projects/components/NewProjectTile/NewProjectTile.js +++ b/examples/complete/material/src/routes/Projects/components/NewProjectTile/NewProjectTile.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import Paper from 'material-ui/Paper' import ContentAddCircle from 'material-ui/svg-icons/content/add-circle' diff --git a/examples/complete/material/src/routes/Projects/components/ProjectTile/ProjectTile.js b/examples/complete/material/src/routes/Projects/components/ProjectTile/ProjectTile.js index 4575f3daa..0d75ebb0e 100644 --- a/examples/complete/material/src/routes/Projects/components/ProjectTile/ProjectTile.js +++ b/examples/complete/material/src/routes/Projects/components/ProjectTile/ProjectTile.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import Paper from 'material-ui/Paper' import { isObject } from 'lodash' import IconButton from 'material-ui/IconButton' diff --git a/examples/complete/material/src/routes/Projects/containers/ProjectsContainer.js b/examples/complete/material/src/routes/Projects/containers/ProjectsContainer.js index e8c681dde..c6a8fe891 100644 --- a/examples/complete/material/src/routes/Projects/containers/ProjectsContainer.js +++ b/examples/complete/material/src/routes/Projects/containers/ProjectsContainer.js @@ -1,4 +1,5 @@ -import React, { Component, cloneElement, PropTypes } from 'react' +import React, { Component, cloneElement } from 'react' +import PropTypes from 'prop-types' import { map } from 'lodash' import { connect } from 'react-redux' import { @@ -28,7 +29,7 @@ const populates = [ @connect( ({ firebase }, { params }) => ({ auth: pathToJS(firebase, 'auth'), - projects: populatedDataToJS(firebase, 'projects', populates) + projects: firebase.data.projects }) ) export default class Projects extends Component { diff --git a/examples/complete/material/src/routes/Projects/routes/Project/components/Project/Project.js b/examples/complete/material/src/routes/Projects/routes/Project/components/Project/Project.js index e8da92b71..6631177ec 100644 --- a/examples/complete/material/src/routes/Projects/routes/Project/components/Project/Project.js +++ b/examples/complete/material/src/routes/Projects/routes/Project/components/Project/Project.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import classes from './Project.scss' export const Project = ({ projects, params: { projectname } }) => ( diff --git a/examples/complete/material/src/routes/Recover/components/RecoverForm/RecoverForm.js b/examples/complete/material/src/routes/Recover/components/RecoverForm/RecoverForm.js index ca673ddd5..41aabd3f4 100644 --- a/examples/complete/material/src/routes/Recover/components/RecoverForm/RecoverForm.js +++ b/examples/complete/material/src/routes/Recover/components/RecoverForm/RecoverForm.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import { Field, reduxForm } from 'redux-form' import RaisedButton from 'material-ui/RaisedButton' import Subheader from 'material-ui/Subheader' diff --git a/examples/complete/material/src/routes/Recover/containers/RecoverContainer.js b/examples/complete/material/src/routes/Recover/containers/RecoverContainer.js index 561b4c679..212f713c9 100644 --- a/examples/complete/material/src/routes/Recover/containers/RecoverContainer.js +++ b/examples/complete/material/src/routes/Recover/containers/RecoverContainer.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { firebaseConnect } from 'react-redux-firebase' import Snackbar from 'material-ui/Snackbar' import Paper from 'material-ui/Paper' diff --git a/examples/complete/material/src/routes/Signup/components/SignupForm/SignupForm.js b/examples/complete/material/src/routes/Signup/components/SignupForm/SignupForm.js index 8757d5621..35cef55b0 100644 --- a/examples/complete/material/src/routes/Signup/components/SignupForm/SignupForm.js +++ b/examples/complete/material/src/routes/Signup/components/SignupForm/SignupForm.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import RaisedButton from 'material-ui/RaisedButton' import { Field, reduxForm } from 'redux-form' import TextField from 'components/TextField' diff --git a/examples/complete/material/src/routes/Signup/containers/SignupContainer.js b/examples/complete/material/src/routes/Signup/containers/SignupContainer.js index 8e173459a..b543faae1 100644 --- a/examples/complete/material/src/routes/Signup/containers/SignupContainer.js +++ b/examples/complete/material/src/routes/Signup/containers/SignupContainer.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { Link } from 'react-router' import GoogleButton from 'react-google-button' import { connect } from 'react-redux' diff --git a/examples/complete/material/src/store/createStore.js b/examples/complete/material/src/store/createStore.js index 0727b6995..f7142252f 100755 --- a/examples/complete/material/src/store/createStore.js +++ b/examples/complete/material/src/store/createStore.js @@ -1,23 +1,23 @@ import { applyMiddleware, compose, createStore } from 'redux' import thunk from 'redux-thunk' -import makeRootReducer from './reducers' import { browserHistory } from 'react-router' -import { reactReduxFirebase, getFirebase } from 'react-redux-firebase' +import { reactReduxFirebase, getFirebase, toJS } from 'react-redux-firebase' +import logger from 'redux-logger' +import * as firebase from 'firebase/app' +import 'firebase/auth' +import 'firebase/database' +import 'firebase/storage' import { firebase as fbConfig, reduxFirebase as reduxConfig } from '../config' -import { version } from '../../package.json' +import makeRootReducer from './reducers' import { updateLocation } from './location' export default (initialState = {}, history) => { - // ====================================================== - // Window Vars Config - // ====================================================== - window.version = version - // ====================================================== // Middleware Configuration // ====================================================== const middleware = [ - thunk.withExtraArgument(getFirebase) + thunk.withExtraArgument(getFirebase), + // logger // This is where you add other middleware like redux-observable ] @@ -32,6 +32,8 @@ export default (initialState = {}, history) => { } } + firebase.initializeApp(fbConfig) + // ====================================================== // Store Instantiation and HMR Setup // ====================================================== @@ -39,8 +41,8 @@ export default (initialState = {}, history) => { makeRootReducer(), initialState, compose( + reactReduxFirebase(firebase, reduxConfig), applyMiddleware(...middleware), - reactReduxFirebase(fbConfig, reduxConfig), ...enhancers ) ) diff --git a/examples/complete/material/src/utils/router.js b/examples/complete/material/src/utils/router.js index 6c75e3b57..f64e78be6 100644 --- a/examples/complete/material/src/utils/router.js +++ b/examples/complete/material/src/utils/router.js @@ -16,10 +16,9 @@ const UNAUTHED_REDIRECT = 'UNAUTHED_REDIRECT' export const UserIsAuthenticated = UserAuthWrapper({ // eslint-disable-line new-cap wrapperDisplayName: 'UserIsAuthenticated', LoadingComponent: LoadingSpinner, - authSelector: ({ firebase }) => pathToJS(firebase, 'auth'), - authenticatingSelector: ({ firebase }) => - (pathToJS(firebase, 'auth') === undefined) || - (pathToJS(firebase, 'isInitializing') === true), + authSelector: ({ firebase: { auth } }) => auth, + authenticatingSelector: ({ firebase: { auth, isInitializing } }) => + auth === undefined || isInitializing === true, predicate: auth => auth !== null, redirectAction: newLoc => (dispatch) => { browserHistory.replace(newLoc) @@ -45,10 +44,9 @@ export const UserIsNotAuthenticated = UserAuthWrapper({ // eslint-disable-line n failureRedirectPath: (state, props) => // redirect to page user was on or to list path props.location.query.redirect || LIST_PATH, - authSelector: ({ firebase }) => pathToJS(firebase, 'auth'), - authenticatingSelector: ({ firebase }) => - (pathToJS(firebase, 'auth') === undefined) || - (pathToJS(firebase, 'isInitializing') === true), + authSelector: ({ firebase: { auth } }) => auth, + authenticatingSelector: ({ firebase: { auth, isInitializing } }) => + auth === undefined || isInitializing === true, predicate: auth => auth === null, redirectAction: newLoc => (dispatch) => { browserHistory.replace(newLoc) diff --git a/examples/complete/material/yarn.lock b/examples/complete/material/yarn.lock index a641463ea..2ded603fe 100644 --- a/examples/complete/material/yarn.lock +++ b/examples/complete/material/yarn.lock @@ -1521,6 +1521,10 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + deep-equal@^1.0.0, deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2160,7 +2164,7 @@ find-up@^1.0.0, find-up@^1.1.2: path-exists "^2.0.0" pinkie-promise "^2.0.0" -firebase@^3.7.5: +firebase@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/firebase/-/firebase-3.9.0.tgz#c4237f50f58eeb25081b1839d6cbf175f8f7ed9b" dependencies: @@ -2943,7 +2947,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -3079,7 +3083,7 @@ loader-utils@^1.0.2: emojis-list "^2.0.0" json5 "^0.5.0" -lodash-es@^4.17.3, lodash-es@^4.2.1: +lodash-es@^4.17.3, lodash-es@^4.17.4, lodash-es@^4.2.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" @@ -4194,7 +4198,7 @@ promise@^7.0.3, promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@15.5.8, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8: +prop-types@15.5.8, prop-types@^15.5.4, prop-types@^15.5.6: version "15.5.8" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394" dependencies: @@ -4313,12 +4317,12 @@ react-google-button@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/react-google-button/-/react-google-button-0.1.0.tgz#dc586526f2bfd5c502088d91f338b9b437b85cd5" -react-redux-firebase@^1.4.0-rc.1: - version "1.4.0-rc.1" - resolved "https://registry.yarnpkg.com/react-redux-firebase/-/react-redux-firebase-1.4.0-rc.1.tgz#cabace23fac997e1e2a30843d7e4d0dfc57f71db" +react-redux-firebase@*: + version "1.4.0" + resolved "https://registry.yarnpkg.com/react-redux-firebase/-/react-redux-firebase-1.4.0.tgz#e3225622bdb4ecfbddfca5ac5df17d2366ab0384" dependencies: es6-promise "^4.1.0" - firebase "^3.7.5" + firebase "^3.9.0" hoist-non-react-statics "^1.2.0" immutable "^3.8.1" jwt-decode "^2.2.0" @@ -4484,6 +4488,20 @@ redux-form@^6.6.1: lodash-es "^4.17.3" prop-types "^15.5.6" +redux-logger@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + dependencies: + deep-diff "^0.3.5" + +redux-persist@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-4.8.0.tgz#17fd998949bdeef9275e4cf60ad5bbe1c73675fc" + dependencies: + json-stringify-safe "^5.0.1" + lodash "^4.17.4" + lodash-es "^4.17.4" + redux-thunk@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" diff --git a/examples/complete/react-native/package.json b/examples/complete/react-native/package.json index 0dc98d97f..43708e3f7 100644 --- a/examples/complete/react-native/package.json +++ b/examples/complete/react-native/package.json @@ -9,11 +9,12 @@ "dev": "concurrently 'npm run watch' 'npm run packager'" }, "dependencies": { + "firebase": "^4.1.2", "react": "15.4.1", "react-native": "0.42.0", "react-native-google-signin": "0.9.0", "react-redux": "^5.0.3", - "react-redux-firebase": "^1.4.0-alpha", + "react-redux-firebase": "^1.4.0", "redux": "^3.6.0" }, "devDependencies": { diff --git a/examples/complete/react-native/src/store.js b/examples/complete/react-native/src/store.js index 14a6b8dd1..222e6c9e2 100644 --- a/examples/complete/react-native/src/store.js +++ b/examples/complete/react-native/src/store.js @@ -3,14 +3,17 @@ import rootReducer from './reducer' import { firebase as fbConfig } from './config' import { reactReduxFirebase } from 'react-redux-firebase' import { AsyncStorage } from 'react-native' +import firebase from 'firebase' + +firebase.initializeApp(fbConfig) export default function configureStore (initialState, history) { const createStoreWithMiddleware = compose( - reactReduxFirebase(fbConfig, + reactReduxFirebase(firebase, { userProfile: 'users', enableLogging: false, - ReactNative: { AsyncStorage }, + // ReactNative: { AsyncStorage }, } ), typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f diff --git a/examples/complete/simple/package.json b/examples/complete/simple/package.json index 2d45f4dee..08cd81954 100644 --- a/examples/complete/simple/package.json +++ b/examples/complete/simple/package.json @@ -6,6 +6,7 @@ "react-scripts": "0.4.1" }, "dependencies": { + "prop-types": "^15.5.8", "react": "^15.3.1", "react-dom": "^15.3.1", "react-redux": "^4.4.5", diff --git a/examples/complete/simple/src/Home.js b/examples/complete/simple/src/Home.js index 534c7c253..23840ff0b 100644 --- a/examples/complete/simple/src/Home.js +++ b/examples/complete/simple/src/Home.js @@ -1,4 +1,5 @@ -import react, { Component, PropTypes } from 'react' +import react, { Component } from 'react' +import PropTypes from 'prop-types' import { connect } from 'react-redux' import { firebaseConnect, diff --git a/examples/complete/simple/src/TodoItem.js b/examples/complete/simple/src/TodoItem.js index 74f916396..84c1d167a 100644 --- a/examples/complete/simple/src/TodoItem.js +++ b/examples/complete/simple/src/TodoItem.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { firebase } from 'react-redux-firebase' import './Todo.css' diff --git a/examples/snippets/decorators/App.js b/examples/snippets/decorators/App.js index f7a1bc01d..144925db3 100644 --- a/examples/snippets/decorators/App.js +++ b/examples/snippets/decorators/App.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { map } from 'lodash' import { connect } from 'react-redux' import { diff --git a/examples/snippets/decorators/TodoItem.js b/examples/snippets/decorators/TodoItem.js index fa954b0d3..7734a1374 100644 --- a/examples/snippets/decorators/TodoItem.js +++ b/examples/snippets/decorators/TodoItem.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { firebaseConnect } from 'react-redux-firebase' import './Todo.css' diff --git a/examples/snippets/multipleQueries/App.js b/examples/snippets/multipleQueries/App.js index 0041c40d4..49e7bc66f 100644 --- a/examples/snippets/multipleQueries/App.js +++ b/examples/snippets/multipleQueries/App.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { map } from 'lodash' import { connect } from 'react-redux' import { diff --git a/examples/snippets/multipleQueries/TodoItem.js b/examples/snippets/multipleQueries/TodoItem.js index 4e5fe2a2b..36aa6ee5c 100644 --- a/examples/snippets/multipleQueries/TodoItem.js +++ b/examples/snippets/multipleQueries/TodoItem.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { firebaseConnect } from 'react-redux-firebase' import './Todo.css' diff --git a/examples/snippets/populates/App.js b/examples/snippets/populates/App.js index afb01cd95..f4c511dda 100644 --- a/examples/snippets/populates/App.js +++ b/examples/snippets/populates/App.js @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import { map } from 'lodash' import { connect } from 'react-redux' import { diff --git a/examples/snippets/stateBasedQuery/App.js b/examples/snippets/stateBasedQuery/App.js index 7bdd04742..df6f0b382 100644 --- a/examples/snippets/stateBasedQuery/App.js +++ b/examples/snippets/stateBasedQuery/App.js @@ -1,4 +1,5 @@ -import react, { Component, PropTypes } from 'react' +import react, { Component } from 'react' +import PropTypes from 'prop-types' import { connect } from 'react-redux' import { isLoaded, isEmpty, pathToJS } from 'react-redux-firebase' import TodosView from './Todos' diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 000000000..a09f298ff --- /dev/null +++ b/index.d.ts @@ -0,0 +1,323 @@ +/** Declaration file generated by dts-gen */ + +export const actionTypes: { + AUTHENTICATION_INIT_FINISHED: string; + AUTHENTICATION_INIT_STARTED: string; + AUTH_UPDATE_ERROR: string; + AUTH_UPDATE_START: string; + AUTH_UPDATE_SUCCESS: string; + EMAIL_UPDATE_ERROR: string; + EMAIL_UPDATE_START: string; + EMAIL_UPDATE_SUCCESS: string; + ERROR: string; + FILE_DELETE_COMPLETE: string; + FILE_DELETE_ERROR: string; + FILE_DELETE_START: string; + FILE_UPLOAD_COMPLETE: string; + FILE_UPLOAD_ERROR: string; + FILE_UPLOAD_PROGRESS: string; + FILE_UPLOAD_START: string; + LOGIN: string; + LOGIN_ERROR: string; + LOGOUT: string; + NO_VALUE: string; + PROFILE_UPDATE_ERROR: string; + PROFILE_UPDATE_START: string; + PROFILE_UPDATE_SUCCESS: string; + SET: string; + SET_PROFILE: string; + START: string; + UNAUTHORIZED_ERROR: string; + UNSET_LISTENER: string; +}; + +export const constants: { + actionTypes: { + AUTHENTICATION_INIT_FINISHED: string; + AUTHENTICATION_INIT_STARTED: string; + AUTH_UPDATE_ERROR: string; + AUTH_UPDATE_START: string; + AUTH_UPDATE_SUCCESS: string; + EMAIL_UPDATE_ERROR: string; + EMAIL_UPDATE_START: string; + EMAIL_UPDATE_SUCCESS: string; + ERROR: string; + FILE_DELETE_COMPLETE: string; + FILE_DELETE_ERROR: string; + FILE_DELETE_START: string; + FILE_UPLOAD_COMPLETE: string; + FILE_UPLOAD_ERROR: string; + FILE_UPLOAD_PROGRESS: string; + FILE_UPLOAD_START: string; + LOGIN: string; + LOGIN_ERROR: string; + LOGOUT: string; + NO_VALUE: string; + PROFILE_UPDATE_ERROR: string; + PROFILE_UPDATE_START: string; + PROFILE_UPDATE_SUCCESS: string; + SET: string; + SET_PROFILE: string; + START: string; + UNAUTHORIZED_ERROR: string; + UNSET_LISTENER: string; + }; + defaultConfig: { + autoPopulateProfile: boolean; + dispatchOnUnsetListener: boolean; + enableEmptyAuthChanges: boolean; + enableLogging: boolean; + enableRedirectHandling: boolean; + setProfilePopulateResults: boolean; + updateProfileOnLogin: boolean; + userProfile: any; + }; + defaultInitProps: string[]; + defaultJWTProps: string[]; + metaParams: string[]; + paramSplitChar: string; + supportedAuthProviders: string[]; +}; + +/** + * Object whose values correspond to different reducer functions. + */ +export interface ConfigObject { + apiKey: string, + authDomain: string, + databaseURL: string, + storageBucket: string +} + +/** + * Object whose values correspond to different reducer functions. + */ +export interface SettingsObject { + apiKey: string, + authDomain: string, + databaseURL: string, + storageBucket: string +} + +export interface listenerConfigFunc { + (): string[] | object[] +} + +export function buildChildList(data: any, list: any, p: any): any; + +export function customToJS(data: any, path: any, custom: any, notSetValue: any): any; + +export function dataToJS(data: any, path: any, notSetValue: any): any; + +export function firebase(...args: any[]): any; + +export function firebaseConnect(...args: any[]): any; + +export function firebaseStateReducer(...args: any[]): any; + +export function fixPath(path: any): any; + +export function getFirebase(): any; + +export function isEmpty(data: any): any; + +export function isLoaded(...args: any[]): any; + +export function orderedToJS(data: any, path: any, notSetValue: any): any; + +export function pathToJS(data: any, path: any, notSetValue: any): any; + +export function populatedDataToJS(data: any, path: any, populates: any, notSetValue: any): any; + +export function reactReduxFirebase(fbConfig: ConfigObject, otherConfig: any, ...args: any[]): any; + +export function reduxFirebase(fbConfig: ConfigObject, otherConfig: any, ...args: any[]): any; + +export function reduxReactFirebase(fbConfig: ConfigObject, otherConfig: any, ...args: any[]): any; + +export function toJS(data: any): any; + +export namespace buildChildList { + const prototype: { + }; + +} + +export namespace customToJS { + const prototype: { + }; + +} + +export namespace dataToJS { + const prototype: { + }; + +} + +export namespace firebase { + const prototype: { + }; + +} + +export namespace firebaseConnect { + const prototype: { + }; + +} + +export namespace firebaseStateReducer { + const prototype: { + }; + +} + +export namespace fixPath { + const prototype: { + }; + +} + +export namespace getFirebase { + const prototype: { + }; + +} + +export namespace helpers { + function buildChildList(data: any, list: any, p: any): any; + + function customToJS(data: any, path: any, custom: any, notSetValue: any): any; + + function dataToJS(data: any, path: any, notSetValue: any): any; + + function fixPath(path: any): any; + + function isEmpty(data: any): any; + + function isLoaded(...args: any[]): any; + + function orderedToJS(data: any, path: any, notSetValue: any): any; + + function pathToJS(data: any, path: any, notSetValue: any): any; + + function populatedDataToJS(data: any, path: any, populates: any, notSetValue: any): any; + + function toJS(data: any): any; + + namespace buildChildList { + const prototype: { + }; + + } + + namespace customToJS { + const prototype: { + }; + + } + + namespace dataToJS { + const prototype: { + }; + + } + + namespace fixPath { + const prototype: { + }; + + } + + namespace isEmpty { + const prototype: { + }; + + } + + namespace isLoaded { + const prototype: { + }; + + } + + namespace orderedToJS { + const prototype: { + }; + + } + + namespace pathToJS { + const prototype: { + }; + + } + + namespace populatedDataToJS { + const prototype: { + }; + + } + + namespace toJS { + const prototype: { + }; + + } + +} + +export namespace isEmpty { + const prototype: { + }; + +} + +export namespace isLoaded { + const prototype: { + }; + +} + +export namespace orderedToJS { + const prototype: { + }; + +} + +export namespace pathToJS { + const prototype: { + }; + +} + +export namespace populatedDataToJS { + const prototype: { + }; + +} + +export namespace reactReduxFirebase { + const prototype: { + }; + +} + +export namespace reduxFirebase { + const prototype: { + }; + +} + +export namespace reduxReactFirebase { + const prototype: { + }; + +} + +export namespace toJS { + const prototype: { + }; + +} diff --git a/package.json b/package.json index bb731628d..db27d8a1b 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "react-redux-firebase", - "version": "1.4.1", + "version": "2.0.0-alpha", "description": "Redux integration for Firebase. Comes with a Higher Order Component for use with React.", - "browser": "dist/react-redux-firebase.js", "main": "lib/index.js", "module": "es/index.js", "jsnext:main": "es/index.js", + "typings": "./index.d.ts", "scripts": { "clean": "rimraf dist", - "lint": "eslint src/** test/**", + "lint": "eslint src/** tests/**", "lint:fix": "npm run lint -- --fix", "test": "mocha -R spec --compilers js:babel-core/register ./tests/setup.js ./tests/**/*.spec.js", "test:cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- ./tests/** --recursive --report lcov --compilers js:babel-register --require babel-polyfill", @@ -19,7 +19,7 @@ "build:umd:min": "cross-env BABEL_ENV=commonjs NODE_ENV=production webpack src/index.js dist/react-redux-firebase.min.js", "build:size": "cross-env SIZE=true BABEL_ENV=commonjs NODE_ENV=production webpack src/index.js dist/react-redux-firebase.min.js", "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min", - "watch": "npm run build:umd -- --watch", + "watch": "npm run build:commonjs -- --watch", "prepublish": "npm run clean && npm run build", "prepush": "npm run lint:fix", "docs:clean": "rimraf _book", @@ -29,25 +29,30 @@ "docs:watch": "npm run docs:prepare && gitbook serve", "docs:publish": "npm run docs:clean && npm run docs:build && cp CNAME _book && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:prescottprue/react-redux-firebase gh-pages --force" }, - "contributors": [ - { - "name": "Prescott Prue", - "email": "sprue.dev@gmail.com", - "url": "https://github.com/prescottprue" - }, - { - "name": "Bojhan", - "url": "https://github.com/Bojhan" - }, - { - "name": "Marshall", - "email": "mmoutenot@gmail.com", - "url": "https://github.com/mmoutenot" - }, - { - "name": "Rahav Lussato", - "url": "https://github.com/RahavLussato" - } + "license": "MIT", + "homepage": "https://github.com/prescottprue/react-redux-firebase#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/prescottprue/react-redux-firebase.git" + }, + "bugs": { + "url": "https://github.com/prescottprue/react-redux-firebase/issues" + }, + "author": { + "name": "Prescott Prue", + "email": "sprue.dev@gmail.com", + "url": "https://github.com/prescottprue" + }, + "keywords": [ + "firebase", + "redux", + "react", + "react-redux", + "redux-firebase", + "react", + "babel", + "hoc", + "redux-react-firebase" ], "dependencies": { "es6-promise": "^4.1.0", @@ -55,7 +60,8 @@ "hoist-non-react-statics": "^1.2.0", "immutable": "^3.8.1", "jwt-decode": "^2.2.0", - "lodash": "^4.17.4" + "lodash": "^4.17.4", + "prop-types": "^15.5.8" }, "peerDependencies": { "react": "^0.14.6 || ^15.0.0" @@ -93,6 +99,7 @@ "eslint-plugin-promise": "^3.5.0", "eslint-plugin-react": "^6.10.3", "eslint-plugin-standard": "^2.2.0", + "firebase-server": "^0.10.1", "gitbook-cli": "^2.3.0", "istanbul": "^1.1.0-alpha.1", "jsdom": "^9.12.0", @@ -104,43 +111,9 @@ "rimraf": "^2.6.1", "sinon": "^2.1.0", "sinon-chai": "^2.9.0", - "webpack": "^1.14.0", + "webpack": "^2.6.1", "webpack-bundle-analyzer": "^2.3.1", + "ws": "^3.0.0", "xmlhttprequest": "^1.8.0" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/prescottprue/react-redux-firebase.git" - }, - "bugs": { - "url": "https://github.com/prescottprue/react-redux-firebase/issues" - }, - "homepage": "https://github.com/prescottprue/react-redux-firebase#readme", - "keywords": [ - "firebase", - "redux", - "react", - "react-redux", - "redux-firebase", - "react", - "babel", - "hoc", - "react-redux-firebase" - ], - "npmName": "react-redux-firebase", - "files": [ - "dist", - "src", - "es", - "lib" - ], - "npmFileMap": [ - { - "basePath": "/dist/", - "files": [ - "*.js" - ] - } - ] + } } diff --git a/src/actions/auth.js b/src/actions/auth.js index 7342d0120..995b06dd6 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -11,12 +11,12 @@ import { } from 'lodash' import jwtDecode from 'jwt-decode' import { actionTypes, defaultJWTProps } from '../constants' +import { getLoginMethodAndParams } from '../utils/auth' import { promisesForPopulate, getPopulateObjs, getChildType } from '../utils/populate' -import { getLoginMethodAndParams } from '../utils/auth' const { SET, @@ -242,6 +242,10 @@ export const init = (dispatch, firebase) => { firebase.auth().onAuthStateChanged(authData => { if (!authData) { + // Run onAuthStateChanged if it exists in config and enableEmptyAuthChanges is set to true + if (isFunction(firebase._.config.onAuthStateChanged) && firebase._.config.enableEmptyAuthChanges) { + firebase._.config.onAuthStateChanged(authData, firebase, dispatch) + } return dispatch({ type: LOGOUT }) } @@ -251,11 +255,12 @@ export const init = (dispatch, firebase) => { dispatchLogin(dispatch, authData) // Run onAuthStateChanged if it exists in config - if (firebase._.config.onAuthStateChanged) { + if (isFunction(firebase._.config.onAuthStateChanged)) { firebase._.config.onAuthStateChanged(authData, firebase, dispatch) } }) + // set redirect result callback if enableRedirectHandling set to true if (firebase._.config.enableRedirectHandling) { firebase.auth().getRedirectResult() .then((authData) => { @@ -361,13 +366,15 @@ export const login = (dispatch, firebase, credentials) => { * @param {Object} firebase - Internal firebase object * @private */ -export const logout = (dispatch, firebase) => { - firebase.auth().signOut() - dispatch({ type: LOGOUT }) - firebase._.authUid = null - unWatchUserProfile(firebase) - return Promise.resolve(firebase) -} +export const logout = (dispatch, firebase) => + firebase.auth() + .signOut() + .then(() => { + dispatch({ type: LOGOUT }) + firebase._.authUid = null + unWatchUserProfile(firebase) + return firebase + }) /** * @description Create a new user in auth and add an account to userProfile root @@ -499,6 +506,122 @@ export const verifyPasswordResetCode = (dispatch, firebase, code) => { }) } +/** + * @description Update user profile + * @param {Function} dispatch - Action dispatch function + * @param {Object} firebase - Internal firebase object + * @param {Object} userData - User data object (response from authenticating) + * @param {Object} profile - Profile data to place in new profile + * @return {Promise} + * @private + */ +export const updateProfile = (dispatch, firebase, profileUpdate) => { + const { database, _: { config, authUid } } = firebase + dispatch({ + type: actionTypes.PROFILE_UPDATE_START, + payload: profileUpdate + }) + return database() + .ref(`${config.userProfile}/${authUid}`) + .update(profileUpdate) + .then((snap) => { + dispatch({ + type: actionTypes.PROFILE_UPDATE_SUCCESS, + payload: snap.val() + }) + }) + .catch((payload) => { + dispatch({ + type: actionTypes.PROFILE_UPDATE_ERROR, + payload + }) + }) +} + + /** + * @description Update Auth Object. Internally calls + * `firebase.auth().currentUser.updateProfile` as seen [in the firebase docs](https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile). + * @param {Function} dispatch - Action dispatch function + * @param {Object} firebase - Internal firebase object + * @param {Object} profileUpdate - Update to be auth object + * @return {Promise} + * @private + */ +export const updateAuth = (dispatch, firebase, authUpdate, updateInProfile) => { + dispatch({ + type: actionTypes.AUTH_UPDATE_START, + payload: authUpdate + }) + if (!firebase.auth().currentUser) { + const msg = 'User must be logged in to update auth.' + dispatch({ + type: actionTypes.AUTH_UPDATE_ERROR, + payload: msg + }) + return Promise.reject(msg) + } + return firebase.auth().currentUser + .updateProfile(authUpdate) + .then((payload) => { + dispatch({ + type: actionTypes.AUTH_UPDATE_SUCCESS, + payload: firebase.auth().currentUser + }) + if (updateInProfile) { + return updateProfile(dispatch, firebase, authUpdate) + } + return payload + }) + .catch((payload) => { + dispatch({ + type: actionTypes.AUTH_UPDATE_ERROR, + payload + }) + }) +} + +/** + * @description Update user's email. Internally calls + * `firebase.auth().currentUser.updateEmail` as seen [in the firebase docs](https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile). + * @param {Function} dispatch - Action dispatch function + * @param {Object} firebase - Internal firebase object + * @param {String} newEmail - Update to be auth object + * @return {Promise} + * @private + */ +export const updateEmail = (dispatch, firebase, newEmail, updateInProfile) => { + dispatch({ + type: actionTypes.EMAIL_UPDATE_START, + payload: newEmail + }) + if (!firebase.auth().currentUser) { + const msg = 'User must be logged in to update email.' + dispatch({ + type: actionTypes.EMAIL_UPDATE_ERROR, + payload: msg + }) + return Promise.reject(msg) + } + return firebase.auth().currentUser + .updateEmail(newEmail) + .then((payload) => { + dispatch({ + type: actionTypes.EMAIL_UPDATE_SUCCESS, + payload: newEmail + }) + if (updateInProfile) { + return updateProfile(dispatch, firebase, { email: newEmail }) + } + return payload + }) + .catch((payload) => { + dispatch({ + type: actionTypes.EMAIL_UPDATE_ERROR, + payload + }) + }) +} + export default { dispatchLoginError, dispatchUnauthorizedError, @@ -512,5 +635,8 @@ export default { createUser, resetPassword, confirmPasswordReset, - verifyPasswordResetCode + verifyPasswordResetCode, + updateAuth, + updateProfile, + updateEmail } diff --git a/src/actions/query.js b/src/actions/query.js index 5addb6322..746b1adf6 100644 --- a/src/actions/query.js +++ b/src/actions/query.js @@ -132,7 +132,8 @@ export const watchEvent = (firebase, dispatch, { type, path, populates, queryPar // TODO: Allow setting of unpopulated data before starting population through config // TODO: Set ordered for populate queries // TODO: Allow config to toggle Combining into one SET action - promisesForPopulate(firebase, data, populates) + const dataKey = snapshot.key + promisesForPopulate(firebase, dataKey, data, populates) .then((results) => { // dispatch child sets first so isLoaded is only set to true for // populatedDataToJS after all data is in redux (Issue #121) @@ -148,7 +149,7 @@ export const watchEvent = (firebase, dispatch, { type, path, populates, queryPar }) dispatch({ type: SET, - path: resultPath, + path: storeAs || resultPath, data, timestamp: Date.now(), requesting: false, @@ -172,8 +173,10 @@ export const watchEvent = (firebase, dispatch, { type, path, populates, queryPar * @param {String} event - Event for which to remove the watcher * @param {String} path - Path of watcher to remove */ -export const unWatchEvent = (firebase, dispatch, event, path, queryId = undefined) => - unsetWatcher(firebase, dispatch, event, path, queryId) +export const unWatchEvent = (firebase, dispatch, { type, path, storeAs, queryId = undefined }) => { + const watchPath = !storeAs ? path : `${path}@${storeAs}` + unsetWatcher(firebase, dispatch, type, watchPath, queryId) +} /** * @description Add watchers to a list of events @@ -192,8 +195,8 @@ export const watchEvents = (firebase, dispatch, events) => * @param {Array} events - List of events for which to remove watchers */ export const unWatchEvents = (firebase, dispatch, events) => - events.forEach(event => - unWatchEvent(firebase, dispatch, event.type, event.path) - ) + events.forEach(event => + unWatchEvent(firebase, dispatch, event) + ) export default { watchEvents, unWatchEvents } diff --git a/src/compose.js b/src/compose.js index ea744462e..640980431 100644 --- a/src/compose.js +++ b/src/compose.js @@ -2,11 +2,10 @@ import * as firebase from 'firebase/app' import 'firebase/auth' import 'firebase/database' import 'firebase/storage' -import { isObject } from 'lodash' +import { createFirebaseInstance } from './createFirebaseInstance' import { defaultConfig } from './constants' import { validateConfig } from './utils' -import { authActions, queryActions, storageActions } from './actions' -let firebaseInstance +import { authActions } from './actions' /** * @name reactReduxFirebase @@ -14,7 +13,7 @@ let firebaseInstance * @description Middleware that handles configuration (placed in redux's * `compose` call) * @property {Object} fbConfig - Object containing Firebase config including - * databaseURL + * databaseURL or Firebase instance * @property {String} fbConfig.apiKey - Firebase apiKey * @property {String} fbConfig.authDomain - Firebase auth domain * @property {String} fbConfig.databaseURL - Firebase database url @@ -31,11 +30,17 @@ let firebaseInstance * auth redirect handling listener. (default: `true`) * @property {Function} config.onAuthStateChanged - Function run when auth state * changes. Argument Pattern: `(authData, firebase, dispatch)` + * @property {Boolean} config.enableEmptyAuthChanges - Whether or not to enable + * empty auth changes. When set to true, `onAuthStateChanged` will be fired with, + * empty auth changes such as undefined on initialization. See + * [#137](https://github.com/prescottprue/react-redux-firebase/issues/137) for + * more details. (default: `false`) * @property {Function} config.onRedirectResult - Function run when redirect * result is returned. Argument Pattern: `(authData, firebase, dispatch)` * @property {Object} config.customAuthParameters - Object for setting which * customAuthParameters are passed to external auth providers. - * @property {Function} config.profileFactory - Factory for modifying how user profile is saved. + * @property {Function} config.profileFactory - Factory for modifying how user + * profile is saved. * @property {Function} config.uploadFileDataFactory - Factory for modifying * how file meta data is written during file uploads * @property {Array|String} config.profileParamsToPopulate - Parameters within @@ -83,423 +88,27 @@ export default (fbConfig, otherConfig) => next => const store = next(reducer, initialState, middleware) const { dispatch } = store - // Combine all configs - const configs = Object.assign({}, defaultConfig, fbConfig, otherConfig) + let firebaseInstance - validateConfig(configs) + // handle firebase instance being passed in as first argument + if (typeof fbConfig.initializeApp === 'function') { + firebaseInstance = createFirebaseInstance(fbConfig, otherConfig, dispatch) + } else { + // Combine all configs + const configs = Object.assign({}, defaultConfig, fbConfig, otherConfig) - // Initialize Firebase - try { - firebase.initializeApp(fbConfig) - } catch (err) {} // silence reinitialize warning (hot-reloading) + validateConfig(configs) - // Enable Logging based on config - if (configs.enableLogging) { - firebase.database.enableLogging(configs.enableLogging) - } - - // Handle react-native - if (configs.ReactNative) { - configs.enableRedirectHandling = false - const { AsyncStorage } = configs.ReactNative - // Stub firebase's internal's with react-native (based on firebase's react-native index file) - firebase.INTERNAL.extendNamespace({ - INTERNAL: { - reactNative: { - AsyncStorage - } - } - }) - } - - const rootRef = firebase.database().ref() - - const instance = Object.defineProperty(firebase, '_', { - value: { - watchers: {}, - config: configs, - authUid: null - }, - writable: true, - enumerable: true, - configurable: true - }) + // Initialize Firebase + try { + firebase.initializeApp(fbConfig) + } catch (err) {} // silence reinitialize warning (hot-reloading) - /** - * @private - * @description Calls a method and attaches meta to value object - * @param {String} method - Method to run with meta attached - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete - * @return {Promise} Containing reference snapshot - */ - const withMeta = (method, path, value, onComplete) => { - if (isObject(value)) { - const prefix = method === 'update' ? 'updated' : 'created' - const dataWithMeta = { - ...value, - [`${prefix}At`]: firebase.database.ServerValue.TIMESTAMP - } - if (instance.auth().currentUser) { - dataWithMeta[`${prefix}By`] = instance.auth().currentUser.uid - } - return rootRef.child(path)[method](dataWithMeta, onComplete) - } - return rootRef.child(path)[method](value, onComplete) + firebaseInstance = createFirebaseInstance(firebase, configs, dispatch) } - /** - * @description Sets data to Firebase. - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component, PropTypes } from 'react' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { set } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const set = (path, value, onComplete) => - rootRef.child(path).set(value, onComplete) - - /** - * @description Sets data to Firebase along with meta data. Currently, - * this includes createdAt and createdBy. *Warning* using this function - * may have unintented consequences (setting createdAt even if data already - * exists) - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - */ - const setWithMeta = (path, value, onComplete) => - withMeta('set', path, value, onComplete) - - /** - * @description Pushes data to Firebase. - * @param {String} path - Path to location on Firebase which to push - * @param {Object|String|Boolean|Number} value - Value to push to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component, PropTypes } from 'react' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { push } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const push = (path, value, onComplete) => - rootRef.child(path).push(value, onComplete) - - /** - * @description Pushes data to Firebase along with meta data. Currently, - * this includes createdAt and createdBy. - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - */ - const pushWithMeta = (path, value, onComplete) => - withMeta('push', path, value, onComplete) - - /** - * @description Updates data on Firebase and sends new data. - * @param {String} path - Path to location on Firebase which to update - * @param {Object|String|Boolean|Number} value - Value to update to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component, PropTypes } from 'react' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { update } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const update = (path, value, onComplete) => - rootRef.child(path).update(value, onComplete) - - /** - * @description Updates data on Firebase along with meta. *Warning* - * using this function may have unintented consequences (setting - * createdAt even if data already exists) - * @param {String} path - Path to location on Firebase which to update - * @param {Object|String|Boolean|Number} value - Value to update to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - */ - const updateWithMeta = (path, value, onComplete) => - withMeta('update', path, value, onComplete) - - /** - * @description Removes data from Firebase at a given path. - * @param {String} path - Path to location on Firebase which to remove - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component, PropTypes } from 'react' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { remove } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const remove = (path, onComplete) => - rootRef.child(path).remove(onComplete) - - /** - * @description Sets data to Firebase only if the path does not already - * exist, otherwise it rejects. - * @param {String} path - Path to location on Firebase which to set - * @param {Object|String|Boolean|Number} value - Value to write to Firebase - * @param {Function} onComplete - Function to run on complete (`not required`) - * @return {Promise} Containing reference snapshot - * @example Basic - * import React, { Component, PropTypes } from 'react' - * import { firebaseConnect } from 'react-redux-firebase' - * const Example = ({ firebase: { uniqueSet } }) => ( - * - * ) - * export default firebaseConnect()(Example) - */ - const uniqueSet = (path, value, onComplete) => - rootRef.child(path) - .once('value') - .then(snap => { - if (snap.val && snap.val() !== null) { - const err = new Error('Path already exists.') - if (onComplete) onComplete(err) - return Promise.reject(err) - } - return rootRef.child(path).set(value, onComplete) - }) - - /** - * @description Upload a file to Firebase Storage with the option to store - * its metadata in Firebase Database - * @param {String} path - Path to location on Firebase which to set - * @param {File} file - File object to upload (usually first element from - * array output of select-file or a drag/drop `onDrop`) - * @param {String} dbPath - Database path to place uploaded file metadata - * @return {Promise} Containing the File object - */ - const uploadFile = (path, file, dbPath) => - storageActions.uploadFile(dispatch, instance, { path, file, dbPath }) - - /** - * @description Upload multiple files to Firebase Storage with the option - * to store their metadata in Firebase Database - * @param {String} path - Path to location on Firebase which to set - * @param {Array} files - Array of File objects to upload (usually from - * a select-file or a drag/drop `onDrop`) - * @param {String} dbPath - Database path to place uploaded files metadata. - * @return {Promise} Containing an array of File objects - */ - const uploadFiles = (path, files, dbPath) => - storageActions.uploadFiles(dispatch, instance, { path, files, dbPath }) - - /** - * @description Delete a file from Firebase Storage with the option to - * remove its metadata in Firebase Database - * @param {String} path - Path to location on Firebase which to set - * @param {String} dbPath - Database path to place uploaded file metadata - * @return {Promise} Containing the File object - */ - const deleteFile = (path, dbPath) => - storageActions.deleteFile(dispatch, instance, { path, dbPath }) - - /** - * @description Watch event. **Note:** this method is used internally - * so examples have not yet been created, and it may not work as expected. - * @param {String} type - Type of watch event - * @param {String} dbPath - Database path on which to setup watch event - * @param {String} storeAs - Name of listener results within redux store - * @return {Promise} - */ - const watchEvent = (type, path, storeAs) => - queryActions.watchEvent(instance, dispatch, { type, path, storeAs }) - - /** - * @description Unset a listener watch event. **Note:** this method is used - * internally so examples have not yet been created, and it may not work - * as expected. - * @param {String} eventName - Type of watch event - * @param {String} eventPath - Database path on which to setup watch event - * @param {String} storeAs - Name of listener results within redux store - * @return {Promise} - */ - const unWatchEvent = (eventName, eventPath, queryId = undefined) => - queryActions.unWatchEvent(instance, dispatch, eventName, eventPath, queryId) - - /** - * @description Logs user into Firebase. For examples, visit the [auth section](/docs/auth.md) - * @param {Object} credentials - Credentials for authenticating - * @param {String} credentials.provider - External provider (google | facebook | twitter) - * @param {String} credentials.type - Type of external authentication (popup | redirect) (only used with provider) - * @param {String} credentials.email - Credentials for authenticating - * @param {String} credentials.password - Credentials for authenticating (only used with email) - * @return {Promise} Containing user's auth data - */ - const login = credentials => - authActions.login(dispatch, instance, credentials) - - /** - * @description Logs user out of Firebase and empties firebase state from - * redux store - * @return {Promise} - */ - const logout = () => - authActions.logout(dispatch, instance) - - /** - * @description Creates a new user in Firebase authentication. If - * `userProfile` config option is set, user profiles will be set to this - * location. - * @param {Object} credentials - Credentials for authenticating - * @param {String} credentials.email - Credentials for authenticating - * @param {String} credentials.password - Credentials for authenticating (only used with email) - * @param {Object} profile - Data to include within new user profile - * @return {Promise} Containing user's auth data - */ - const createUser = (credentials, profile) => - authActions.createUser(dispatch, instance, credentials, profile) - - /** - * @description Sends password reset email - * @param {Object} credentials - Credentials for authenticating - * @param {String} credentials.email - Credentials for authenticating - * @return {Promise} - */ - const resetPassword = (credentials) => - authActions.resetPassword(dispatch, instance, credentials) - - /** - * @description Confirm that a user's password has been reset - * @param {String} code - Password reset code to verify - * @param {String} password - New Password to confirm reset to - * @return {Promise} - */ - const confirmPasswordReset = (code, password) => - authActions.confirmPasswordReset(dispatch, instance, code, password) - - /** - * @description Verify that a password reset code from a password reset - * email is valid - * @param {String} code - Password reset code to verify - * @return {Promise} Containing user auth info - */ - const verifyPasswordResetCode = (code) => - authActions.verifyPasswordResetCode(dispatch, instance, code) - - /** - * @name ref - * @description Firebase ref function - * @return {database.Reference} - */ - /** - * @name database - * @description Firebase database service instance including all Firebase storage methods - * @return {Database} Firebase database service - */ - /** - * @name storage - * @description Firebase storage service instance including all Firebase storage methods - * @return {Storage} Firebase storage service - */ - /** - * @name auth - * @description Firebase auth service instance including all Firebase auth methods - * @return {Auth} - */ - firebase.helpers = { - ref: path => firebase.database().ref(path), - set, - setWithMeta, - uniqueSet, - push, - pushWithMeta, - remove, - update, - updateWithMeta, - login, - logout, - uploadFile, - uploadFiles, - deleteFile, - createUser, - resetPassword, - confirmPasswordReset, - verifyPasswordResetCode, - watchEvent, - unWatchEvent, - storage: () => firebase.storage() - } - - authActions.init(dispatch, instance) - - store.firebase = instance - firebaseInstance = Object.assign({}, instance, instance.helpers) + authActions.init(dispatch, firebaseInstance) + store.firebase = firebaseInstance return store } - -/** - * @external - * @description Expose Firebase instance created internally. Useful for - * integrations into external libraries such as redux-thunk and redux-observable. - * @example redux-thunk integration - * import { applyMiddleware, compose, createStore } from 'redux'; - * import thunk from 'redux-thunk'; - * import { reactReduxFirebase } from 'react-redux-firebase'; - * import makeRootReducer from './reducers'; - * import { getFirebase } from 'react-redux-firebase'; - * - * const fbConfig = {} // your firebase config - * - * const store = createStore( - * makeRootReducer(), - * initialState, - * compose( - * applyMiddleware([ - * // Pass getFirebase function as extra argument - * thunk.withExtraArgument(getFirebase) - * ]), - * reactReduxFirebase(fbConfig) - * ) - * ); - * // then later - * export const addTodo = (newTodo) => - * (dispatch, getState, getFirebase) => { - * const firebase = getFirebase() - * firebase - * .push('todos', newTodo) - * .then(() => { - * dispatch({ type: 'SOME_ACTION' }) - * }) - * }; - * - */ -export const getFirebase = () => { - // TODO: Handle recieveing config and creating firebase instance if it doesn't exist - /* istanbul ignore next: Firebase instance always exists during tests */ - if (!firebaseInstance) { - throw new Error('Firebase instance does not yet exist. Check your compose function.') // eslint-disable-line no-console - } - // TODO: Create new firebase here with config passed in - return firebaseInstance -} diff --git a/src/connect.js b/src/connect.js deleted file mode 100644 index 4636b398c..000000000 --- a/src/connect.js +++ /dev/null @@ -1,110 +0,0 @@ -import React, { Component, PropTypes } from 'react' -import { isEqual } from 'lodash' -import hoistStatics from 'hoist-non-react-statics' -import { watchEvents, unWatchEvents } from './actions/query' -import { getEventsFromInput, createCallable } from './utils' - -/** - * @name firebaseConnect - * @extends React.Component - * @description Higher Order Component that automatically listens/unListens - * to provided firebase paths using React's Lifecycle hooks. - * @param {Array} watchArray - Array of objects or strings for paths to sync from Firebase. Can also be a function that returns the array. The function is passed the current props and the firebase object. - * @return {Function} - that accepts a component to wrap and returns the wrapped component - * @example Basic - * // this.props.firebase set on App component as firebase object with helpers - * import { firebaseConnect } from 'react-redux-firebase' - * export default firebaseConnect()(App) - * @example Data - * import { connect } from 'react-redux' - * import { firebaseConnect, dataToJS } from 'react-redux-firebase' - * - * // sync /todos from firebase into redux - * const fbWrapped = firebaseConnect([ - * 'todos' - * ])(App) - * - * // pass todos list from redux as this.props.todosList - * export default connect(({ firebase }) => ({ - * todosList: dataToJS(firebase, 'todos'), - * profile: pathToJS(firebase, 'profile'), // pass profile data as this.props.profile - * auth: pathToJS(firebase, 'auth') // pass auth data as this.props.auth - * }))(fbWrapped) - * @example Data that depends on props - * import { connect } from 'react-redux' - * import { firebaseConnect, dataToJS } from 'react-redux-firebase' - * - * // sync /todos from firebase into redux - * const fbWrapped = firebaseConnect((props, firebase) => ([ - * `todos/${firebase.database().currentUser.uid}/${props.type}` - * ])(App) - * - * // pass todos list for the specified type of todos from redux as `this.props.todosList` - * export default connect(({ firebase, type }) => ({ - * todosList: dataToJS(firebase, `data/todos/${firebase.getIn(['auth', 'uid'])}/${type}`), - * profile: pathToJS(firebase, 'profile'), // pass profile data as this.props.profile - * auth: pathToJS(firebase, 'auth') // pass auth data as this.props.auth - * }))(fbWrapped) - */ -export default (dataOrFn = []) => WrappedComponent => { - class FirebaseConnect extends Component { - constructor (props, context) { - super(props, context) - this._firebaseEvents = [] - this.firebase = null - } - - static contextTypes = { - store: PropTypes.object.isRequired - }; - - componentWillMount () { - const { firebase, dispatch } = this.context.store - - // Allow function to be passed - const inputAsFunc = createCallable(dataOrFn) - this.prevData = inputAsFunc(this.props, firebase) - - const { ref, helpers, storage, database, auth } = firebase - this.firebase = { ref, storage, database, auth, ...helpers } - - this._firebaseEvents = getEventsFromInput(this.prevData) - - watchEvents(firebase, dispatch, this._firebaseEvents) - } - - componentWillUnmount () { - const { firebase, dispatch } = this.context.store - unWatchEvents(firebase, dispatch, this._firebaseEvents) - } - - componentWillReceiveProps (np) { - const { firebase, dispatch } = this.context.store - const inputAsFunc = createCallable(dataOrFn) - const data = inputAsFunc(np, firebase) - - // Handle a data parameter having changed - if (!isEqual(data, this.prevData)) { - this.prevData = data - // UnWatch all current events - unWatchEvents(firebase, dispatch, this._firebaseEvents) - // Get watch events from new data - this._firebaseEvents = getEventsFromInput(data) - // Watch new events - watchEvents(firebase, dispatch, this._firebaseEvents) - } - } - - render () { - return ( - - ) - } - } - - return hoistStatics(FirebaseConnect, WrappedComponent) -} diff --git a/src/constants.js b/src/constants.js index 9679f7ebe..8b2b9ded0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -13,6 +13,7 @@ export const actionsPrefix = '@@reactReduxFirebase' * @description Object containing all action types * @property {String} START - `@@reactReduxFirebase/START` * @property {String} SET - `@@reactReduxFirebase/SET` + * @property {String} SET_ORDERED - `@@reactReduxFirebase/SET_ORDERED` * @property {String} SET_PROFILE - `@@reactReduxFirebase/SET_PROFILE` * @property {String} LOGIN - `@@reactReduxFirebase/LOGIN` * @property {String} LOGOUT - `@@reactReduxFirebase/LOGOUT` @@ -29,6 +30,15 @@ export const actionsPrefix = '@@reactReduxFirebase' * @property {String} FILE_DELETE_START - `@@reactReduxFirebase/FILE_DELETE_START` * @property {String} FILE_DELETE_ERROR - `@@reactReduxFirebase/FILE_DELETE_ERROR` * @property {String} FILE_DELETE_COMPLETE - `@@reactReduxFirebase/FILE_DELETE_COMPLETE` + * @property {String} AUTH_UPDATE_START - `@@reactReduxFirebase/AUTH_UPDATE_START` + * @property {String} AUTH_UPDATE_ERROR - `@@reactReduxFirebase/AUTH_UPDATE_ERROR` + * @property {String} AUTH_UPDATE_COMPLETE - `@@reactReduxFirebase/AUTH_UPDATE_COMPLETE` + * @property {String} PROFILE_UPDATE_START - `@@reactReduxFirebase/PROFILE_UPDATE_START` + * @property {String} PROFILE_UPDATE_ERROR - `@@reactReduxFirebase/PROFILE_UPDATE_ERROR` + * @property {String} PROFILE_UPDATE_COMPLETE - `@@reactReduxFirebase/PROFILE_UPDATE_COMPLETE` + * @property {String} EMAIL_UPDATE_START - `@@reactReduxFirebase/EMAIL_UPDATE_START` + * @property {String} EMAIL_UPDATE_ERROR - `@@reactReduxFirebase/EMAIL_UPDATE_ERROR` + * @property {String} EMAIL_UPDATE_COMPLETE - `@@reactReduxFirebase/EMAIL_UPDATE_COMPLETE` * @example * import { actionTypes } from 'react-redux-firebase' * actionTypes.SET === '@@reactReduxFirebase/SET' // true @@ -52,7 +62,16 @@ export const actionTypes = { FILE_UPLOAD_COMPLETE: `${actionsPrefix}/FILE_UPLOAD_COMPLETE`, FILE_DELETE_START: `${actionsPrefix}/FILE_DELETE_START`, FILE_DELETE_ERROR: `${actionsPrefix}/FILE_DELETE_ERROR`, - FILE_DELETE_COMPLETE: `${actionsPrefix}/FILE_DELETE_COMPLETE` + FILE_DELETE_COMPLETE: `${actionsPrefix}/FILE_DELETE_COMPLETE`, + AUTH_UPDATE_START: `${actionsPrefix}/AUTH_UPDATE_START`, + AUTH_UPDATE_SUCCESS: `${actionsPrefix}/AUTH_UPDATE_SUCCESS`, + AUTH_UPDATE_ERROR: `${actionsPrefix}/AUTH_UPDATE_ERROR`, + PROFILE_UPDATE_START: `${actionsPrefix}/PROFILE_UPDATE_START`, + PROFILE_UPDATE_SUCCESS: `${actionsPrefix}/PROFILE_UPDATE_SUCCESS`, + PROFILE_UPDATE_ERROR: `${actionsPrefix}/PROFILE_UPDATE_ERROR`, + EMAIL_UPDATE_START: `${actionsPrefix}/EMAIL_UPDATE_START`, + EMAIL_UPDATE_SUCCESS: `${actionsPrefix}/EMAIL_UPDATE_SUCCESS`, + EMAIL_UPDATE_ERROR: `${actionsPrefix}/EMAIL_UPDATE_ERROR` } /** @constant @@ -66,6 +85,11 @@ export const actionTypes = { * @property {Boolean} enableRedirectHandling - `true` Whether or not to enable * redirect handling. This must be disabled if environment is not http/https * such as with react-native. + * @property {Boolean} enableEmptyAuthChanges - `false` Whether or not to enable + * empty auth changes. When set to true, `onAuthStateChanged` will be fired with, + * empty auth changes such as `undefined` on initialization + * (see [#137](https://github.com/prescottprue/react-redux-firebase/issues/137)). + * Requires `v1.5.0-alpha` or higher. * @property {Boolean} autoPopulateProfile - `true` Whether or not to * automatically populate profile with data loaded through * profileParamsToPopulate config. @@ -74,11 +98,11 @@ export const actionTypes = { * the data path. For example: role paramter on profile populated from 'roles' * root. True will call SET_PROFILE as well as a SET action with the role that * is loaded (places it in data/roles). - * @property {Boolean} distpatchOnUnsetListener - `false` Whether or not to + * @property {Boolean} dispatchOnUnsetListener - `false` Whether or not to * dispatch UNSET_LISTENER when disabling listeners for a specific path. USE WITH CAUTION * Setting this to true allows an action to be called that removes data * from redux (which might not always be expected). - * @type {Array} + * @type {Object} */ export const defaultConfig = { userProfile: null, @@ -87,7 +111,8 @@ export const defaultConfig = { enableRedirectHandling: true, autoPopulateProfile: true, setProfilePopulateResults: false, - distpatchOnUnsetListener: false + dispatchOnUnsetListener: false, + enableEmptyAuthChanges: false } /** @constant @@ -156,13 +181,3 @@ export default { metaParams, paramSplitChar } - -module.exports = { - defaultJWTProps, - actionTypes, - defaultConfig, - supportedAuthProviders, - defaultInitProps, - metaParams, - paramSplitChar -} diff --git a/src/createFirebaseConnect.js b/src/createFirebaseConnect.js new file mode 100644 index 000000000..b2aac37ae --- /dev/null +++ b/src/createFirebaseConnect.js @@ -0,0 +1,120 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { isEqual } from 'lodash' +import hoistStatics from 'hoist-non-react-statics' +import { watchEvents, unWatchEvents } from './actions/query' +import { getEventsFromInput, createCallable } from './utils' + +/** + * @name createFirebaseConnect + * @description WARNING!! Advanced feature, and only be used when needing to + * access a firebase instance created under a different store key + * @param {String} storeKey - Name of key of store to connect to (store that contains state.firebase) + * @return {Function} - that returns a firebaseConnect function, which is later used to wrap a component + * @example Data + * import { connect } from 'react-redux' + * import { createFirebaseConnect } from 'react-redux-firebase' + * + * // sync /todos from firebase (in other store) into redux + * const fbWrapped = createFirebaseConnect('someOtherName')(['todos']) + * + * // pass todos list from redux as this.props.todosList + * export default connect(({ firebase: data: { todos }, auth, profile }) => ({ + * todos, + * profile, // pass profile data as this.props.profile + * auth // pass auth data as this.props.auth + * }))(fbWrapped) + */ +export const createFirebaseConnect = (storeKey = 'store') => (dataOrFn = []) => WrappedComponent => { + /** + * @name firebaseConnect + * @extends React.Component + * @description Higher Order Component that automatically listens/unListens + * to provided firebase paths using React's Lifecycle hooks. + * @param {Array} watchArray - Array of objects or strings for paths to sync + * from Firebase. Can also be a function that returns the array. The function + * is passed the current props and the firebase object. + * @return {Function} - that accepts a component to wrap and returns the wrapped component + * @example Basic + * // this.props.firebase set on App component as firebase object with helpers + * import { firebaseConnect } from 'react-redux-firebase' + * export default firebaseConnect()(App) + * @example Data + * import { connect } from 'react-redux' + * import { firebaseConnect, dataToJS } from 'react-redux-firebase' + * + * // sync /todos from firebase into redux + * const fbWrapped = firebaseConnect([ + * 'todos' + * ])(App) + * + * // pass todos list from redux as this.props.todosList + * export default connect(({ firebase: data: { todos }, auth, profile }) => ({ + * todos, + * profile, // pass profile data as this.props.profile + * auth // pass auth data as this.props.auth + * }))(fbWrapped) + */ + class FirebaseConnect extends Component { + constructor (props, context) { + super(props, context) + this._firebaseEvents = [] + this.firebase = null + } + + static contextTypes = { + [storeKey]: PropTypes.object.isRequired + }; + + componentWillMount () { + const { firebase, dispatch } = this.context[storeKey] + + // Allow function to be passed + const inputAsFunc = createCallable(dataOrFn) + this.prevData = inputAsFunc(this.props, firebase) + + const { ref, helpers, storage, database, auth } = firebase + this.firebase = { ref, storage, database, auth, ...helpers } + + this._firebaseEvents = getEventsFromInput(this.prevData) + + watchEvents(firebase, dispatch, this._firebaseEvents) + } + + componentWillUnmount () { + const { firebase, dispatch } = this.context.store + unWatchEvents(firebase, dispatch, this._firebaseEvents) + } + + componentWillReceiveProps (np) { + const { firebase, dispatch } = this.context.store + const inputAsFunc = createCallable(dataOrFn) + const data = inputAsFunc(np, firebase) + + // Handle a data parameter having changed + if (!isEqual(data, this.prevData)) { + this.prevData = data + // UnWatch all current events + unWatchEvents(firebase, dispatch, this._firebaseEvents) + // Get watch events from new data + this._firebaseEvents = getEventsFromInput(data) + // Watch new events + watchEvents(firebase, dispatch, this._firebaseEvents) + } + } + + render () { + return ( + + ) + } + } + + return hoistStatics(FirebaseConnect, WrappedComponent) +} + +export default createFirebaseConnect() diff --git a/src/createFirebaseInstance.js b/src/createFirebaseInstance.js new file mode 100644 index 000000000..40db567a5 --- /dev/null +++ b/src/createFirebaseInstance.js @@ -0,0 +1,398 @@ +import { isObject } from 'lodash' +import { authActions, queryActions, storageActions } from './actions' + +let firebaseInstance + +export const createFirebaseInstance = (firebase, configs, dispatch) => { + // Enable Logging based on config + if (configs.enableLogging) { + firebase.database.enableLogging(configs.enableLogging) + } + + const rootRef = firebase.database().ref() + + const instance = Object.defineProperty(firebase, '_', { + value: { + watchers: {}, + config: configs, + authUid: null + }, + writable: true, + enumerable: true, + configurable: true + }) + + /** + * @private + * @description Calls a method and attaches meta to value object + * @param {String} method - Method to run with meta attached + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete + * @return {Promise} Containing reference snapshot + */ + const withMeta = (method, path, value, onComplete) => { + if (isObject(value)) { + const prefix = method === 'update' ? 'updated' : 'created' + const dataWithMeta = { + ...value, + [`${prefix}At`]: firebase.database.ServerValue.TIMESTAMP + } + if (instance.auth().currentUser) { + dataWithMeta[`${prefix}By`] = instance.auth().currentUser.uid + } + return rootRef.child(path)[method](dataWithMeta, onComplete) + } + return rootRef.child(path)[method](value, onComplete) + } + + /** + * @description Sets data to Firebase. + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component, PropTypes } from 'react' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { set } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const set = (path, value, onComplete) => + rootRef.child(path).set(value, onComplete) + + /** + * @description Sets data to Firebase along with meta data. Currently, + * this includes createdAt and createdBy. *Warning* using this function + * may have unintented consequences (setting createdAt even if data already + * exists) + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + */ + const setWithMeta = (path, value, onComplete) => + withMeta('set', path, value, onComplete) + + /** + * @description Pushes data to Firebase. + * @param {String} path - Path to location on Firebase which to push + * @param {Object|String|Boolean|Number} value - Value to push to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component, PropTypes } from 'react' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { push } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const push = (path, value, onComplete) => + rootRef.child(path).push(value, onComplete) + + /** + * @description Pushes data to Firebase along with meta data. Currently, + * this includes createdAt and createdBy. + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + */ + const pushWithMeta = (path, value, onComplete) => + withMeta('push', path, value, onComplete) + + /** + * @description Updates data on Firebase and sends new data. + * @param {String} path - Path to location on Firebase which to update + * @param {Object|String|Boolean|Number} value - Value to update to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component, PropTypes } from 'react' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { update } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const update = (path, value, onComplete) => + rootRef.child(path).update(value, onComplete) + + /** + * @description Updates data on Firebase along with meta. *Warning* + * using this function may have unintented consequences (setting + * createdAt even if data already exists) + * @param {String} path - Path to location on Firebase which to update + * @param {Object|String|Boolean|Number} value - Value to update to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + */ + const updateWithMeta = (path, value, onComplete) => + withMeta('update', path, value, onComplete) + + /** + * @description Removes data from Firebase at a given path. + * @param {String} path - Path to location on Firebase which to remove + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component, PropTypes } from 'react' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { remove } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const remove = (path, onComplete) => + rootRef.child(path).remove(onComplete) + + /** + * @description Sets data to Firebase only if the path does not already + * exist, otherwise it rejects. + * @param {String} path - Path to location on Firebase which to set + * @param {Object|String|Boolean|Number} value - Value to write to Firebase + * @param {Function} onComplete - Function to run on complete (`not required`) + * @return {Promise} Containing reference snapshot + * @example Basic + * import React, { Component, PropTypes } from 'react' + * import { firebaseConnect } from 'react-redux-firebase' + * const Example = ({ firebase: { uniqueSet } }) => ( + * + * ) + * export default firebaseConnect()(Example) + */ + const uniqueSet = (path, value, onComplete) => + rootRef.child(path) + .once('value') + .then(snap => { + if (snap.val && snap.val() !== null) { + const err = new Error('Path already exists.') + if (onComplete) onComplete(err) + return Promise.reject(err) + } + return rootRef.child(path).set(value, onComplete) + }) + + /** + * @description Upload a file to Firebase Storage with the option to store + * its metadata in Firebase Database + * @param {String} path - Path to location on Firebase which to set + * @param {File} file - File object to upload (usually first element from + * array output of select-file or a drag/drop `onDrop`) + * @param {String} dbPath - Database path to place uploaded file metadata + * @return {Promise} Containing the File object + */ + const uploadFile = (path, file, dbPath) => + storageActions.uploadFile(dispatch, instance, { path, file, dbPath }) + + /** + * @description Upload multiple files to Firebase Storage with the option + * to store their metadata in Firebase Database + * @param {String} path - Path to location on Firebase which to set + * @param {Array} files - Array of File objects to upload (usually from + * a select-file or a drag/drop `onDrop`) + * @param {String} dbPath - Database path to place uploaded files metadata. + * @return {Promise} Containing an array of File objects + */ + const uploadFiles = (path, files, dbPath) => + storageActions.uploadFiles(dispatch, instance, { path, files, dbPath }) + + /** + * @description Delete a file from Firebase Storage with the option to + * remove its metadata in Firebase Database + * @param {String} path - Path to location on Firebase which to set + * @param {String} dbPath - Database path to place uploaded file metadata + * @return {Promise} Containing the File object + */ + const deleteFile = (path, dbPath) => + storageActions.deleteFile(dispatch, instance, { path, dbPath }) + + /** + * @description Watch event. **Note:** this method is used internally + * so examples have not yet been created, and it may not work as expected. + * @param {String} type - Type of watch event + * @param {String} dbPath - Database path on which to setup watch event + * @param {String} storeAs - Name of listener results within redux store + * @return {Promise} + */ + const watchEvent = (type, path, storeAs) => + queryActions.watchEvent(instance, dispatch, { type, path, storeAs }) + + /** + * @description Unset a listener watch event. **Note:** this method is used + * internally so examples have not yet been created, and it may not work + * as expected. + * @param {String} eventName - Type of watch event + * @param {String} eventPath - Database path on which to setup watch event + * @param {String} storeAs - Name of listener results within redux store + * @return {Promise} + */ + const unWatchEvent = (eventName, eventPath, queryId = undefined) => + queryActions.unWatchEvent(instance, dispatch, eventName, eventPath, queryId) + + /** + * @description Logs user into Firebase. For examples, visit the [auth section](/docs/auth.md) + * @param {Object} credentials - Credentials for authenticating + * @param {String} credentials.provider - External provider (google | facebook | twitter) + * @param {String} credentials.type - Type of external authentication (popup | redirect) (only used with provider) + * @param {String} credentials.email - Credentials for authenticating + * @param {String} credentials.password - Credentials for authenticating (only used with email) + * @return {Promise} Containing user's auth data + */ + const login = credentials => + authActions.login(dispatch, instance, credentials) + + /** + * @description Logs user out of Firebase and empties firebase state from + * redux store + * @return {Promise} + */ + const logout = () => + authActions.logout(dispatch, instance) + + /** + * @description Creates a new user in Firebase authentication. If + * `userProfile` config option is set, user profiles will be set to this + * location. + * @param {Object} credentials - Credentials for authenticating + * @param {String} credentials.email - Credentials for authenticating + * @param {String} credentials.password - Credentials for authenticating (only used with email) + * @param {Object} profile - Data to include within new user profile + * @return {Promise} Containing user's auth data + */ + const createUser = (credentials, profile) => + authActions.createUser(dispatch, instance, credentials, profile) + + /** + * @description Sends password reset email + * @param {Object} credentials - Credentials for authenticating + * @param {String} credentials.email - Credentials for authenticating + * @return {Promise} + */ + const resetPassword = (credentials) => + authActions.resetPassword(dispatch, instance, credentials) + + /** + * @description Confirm that a user's password has been reset + * @param {String} code - Password reset code to verify + * @param {String} password - New Password to confirm reset to + * @return {Promise} + */ + const confirmPasswordReset = (code, password) => + authActions.confirmPasswordReset(dispatch, instance, code, password) + + /** + * @description Verify that a password reset code from a password reset + * email is valid + * @param {String} code - Password reset code to verify + * @return {Promise} Containing user auth info + */ + const verifyPasswordResetCode = (code) => + authActions.verifyPasswordResetCode(dispatch, instance, code) + + /** + * @name ref + * @description Firebase ref function + * @return {database.Reference} + */ + /** + * @name database + * @description Firebase database service instance including all Firebase storage methods + * @return {Database} Firebase database service + */ + /** + * @name storage + * @description Firebase storage service instance including all Firebase storage methods + * @return {Storage} Firebase storage service + */ + /** + * @name auth + * @description Firebase auth service instance including all Firebase auth methods + * @return {Auth} + */ + return { + ...instance, + helpers: { + ref: path => firebase.database().ref(path), + storage: () => firebase.storage(), + set, + setWithMeta, + uniqueSet, + push, + pushWithMeta, + remove, + update, + updateWithMeta, + login, + logout, + uploadFile, + uploadFiles, + deleteFile, + createUser, + resetPassword, + confirmPasswordReset, + verifyPasswordResetCode, + watchEvent, + unWatchEvent + } + } +} + +/** + * @external + * @description Expose Firebase instance created internally. Useful for + * integrations into external libraries such as redux-thunk and redux-observable. + * @example redux-thunk integration + * import { applyMiddleware, compose, createStore } from 'redux'; + * import thunk from 'redux-thunk'; + * import { reactReduxFirebase } from 'react-redux-firebase'; + * import makeRootReducer from './reducers'; + * import { getFirebase } from 'react-redux-firebase'; + * + * const fbConfig = {} // your firebase config + * + * const store = createStore( + * makeRootReducer(), + * initialState, + * compose( + * applyMiddleware([ + * // Pass getFirebase function as extra argument + * thunk.withExtraArgument(getFirebase) + * ]), + * reactReduxFirebase(fbConfig) + * ) + * ); + * // then later + * export const addTodo = (newTodo) => + * (dispatch, getState, getFirebase) => { + * const firebase = getFirebase() + * firebase + * .push('todos', newTodo) + * .then(() => { + * dispatch({ type: 'SOME_ACTION' }) + * }) + * }; + * + */ +export const getFirebase = () => { + // TODO: Handle recieveing config and creating firebase instance if it doesn't exist + /* istanbul ignore next: Firebase instance always exists during tests */ + if (!firebaseInstance) { + throw new Error('Firebase instance does not yet exist. Check your compose function.') // eslint-disable-line no-console + } + // TODO: Create new firebase here with config passed in + return firebaseInstance +} diff --git a/src/helpers.js b/src/helpers.js index be893b45b..31a191532 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,30 +1,34 @@ import { size, map, - some, - first, - drop, mapValues, + get, + isFunction, + every, + has, + set, + split, + last, reduce, - isString, - defaultsDeep + defaultsDeep, + isString } from 'lodash' import { getPopulateObjs } from './utils/populate' -import { metaParams, paramSplitChar } from './constants' /** * @description Detect whether items are loaded yet or not * @param {Object} item - Item to check loaded status of. A comma seperated list is also acceptable. * @return {Boolean} Whether or not item is loaded * @example - * import React, { Component, PropTypes } from 'react' + * import React, { Component } from 'react' + * import PropTypes from 'prop-types' * import { connect } from 'react-redux' - * import { firebaseConnect, isLoaded, dataToJS } from 'react-redux-firebase' + * import { firebaseConnect, isLoaded } from 'react-redux-firebase' * * @firebaseConnect(['/todos']) * @connect( - * ({ firebase }) => ({ - * todos: dataToJS(firebase, '/todos'), + * ({ firebase: { data: { todos } } }) => ({ + * todos, * }) * ) * class Todos extends Component { @@ -57,14 +61,15 @@ export const isLoaded = function () { * @param {Object} item - Item to check loaded status of. A comma seperated list is also acceptable. * @return {Boolean} Whether or not item is empty * @example - * import React, { Component, PropTypes } from 'react' + * import React, { Component } from 'react' + * import PropTypes from 'prop-types' * import { connect } from 'react-redux' - * import { firebaseConnect, isEmpty, dataToJS } from 'react-redux-firebase' + * import { firebaseConnect, isEmpty } from 'react-redux-firebase' * * @firebaseConnect(['/todos']) * @connect( - * ({ firebase }) => ({ - * todos: dataToJS(firebase, '/todos'), + * ({ firebase: { data: { todos } } }) => ({ + * todos // state.firebase.data.todos from redux passed as todos prop * }) * ) * class Todos extends Component { @@ -86,156 +91,14 @@ export const isLoaded = function () { */ export const isEmpty = data => !(data && size(data)) -/** - * @description Fix path by adding "/" to path if needed - * @param {String} path - Path string to fix - * @return {String} - Fixed path - * @private - */ -export const fixPath = path => - ((path.substring(0, 1) === '/') ? '' : '/') + path - -/** - * @description Convert Immutable Map to a Javascript object - * @param {Object} data - Immutable Map to be converted to JS object (state.firebase) - * @return {Object} data - Javascript version of Immutable Map - * @return {Object} Data located at path within Immutable Map - */ -export const toJS = data => - data && data.toJS - ? data.toJS() - : data - -/** - * @description Convert parameter from Immutable Map to a Javascript object - * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) - * @param {String} path - Path from state.firebase to convert to JS object - * @param {Object|String|Boolean} notSetValue - Value to use if data is not available - * @return {Object} Data located at path within Immutable Map - * @example Basic - * import { connect } from 'react-redux' - * import { firebaseConnect, pathToJS } from 'react-redux-firebase' - * - * @firebaseConnect() - * @connect(({ firebase }) => ({ - * profile: pathToJS(firebase, 'profile'), - * auth: pathToJS(firebase, 'auth') - * }) - * export default class MyComponent extends Component { - * ... - */ -export const pathToJS = (data, path, notSetValue) => { - if (!data) { - return notSetValue - } - const pathArr = fixPath(path).split(/\//).slice(1) - - if (data.getIn) { - // Handle meta params (stored by string key) - if (some(metaParams, (v) => pathArr.indexOf(v) !== -1)) { - return toJS( - data.getIn([ - first(pathArr), - drop(pathArr).join(paramSplitChar) - ], notSetValue) - ) - } - return toJS(data.getIn(pathArr, notSetValue)) - } - - return data -} - -/** - * @description Convert parameter under "data" path of Immutable Map to a Javascript object. - * **NOTE:** Setting a default value will cause `isLoaded` to always return true - * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) - * @param {String} path - Path of parameter to load - * @param {Object|String|Boolean} notSetValue - Value to return if value is not - * found in redux. This will cause `isLoaded` to always return true (since - * value is set from the start). - * @return {Object} Data located at path within Immutable Map - * @example Basic - * import { connect } from 'react-redux' - * import { firebaseConnect, dataToJS } from 'react-redux-firebase' - * - * @firebaseConnect(['/todos']) - * @connect(({ firebase }) => ({ - * // this.props.todos loaded from state.firebase.data.todos - * todos: dataToJS(firebase, 'todos') - * }) - * @example Default Value - * import { connect } from 'react-redux' - * import { firebaseConnect, dataToJS } from 'react-redux-firebase' - * const defaultValue = { - * 1: { - * text: 'Example Todo' - * } - * } - * @firebaseConnect(['/todos']) - * @connect(({ firebase }) => ({ - * // this.props.todos loaded from state.firebase.data.todos - * todos: dataToJS(firebase, 'todos', defaultValue) - * }) - */ -export const dataToJS = (data, path, notSetValue) => { - if (!data) { - return notSetValue - } - - const pathArr = `/data${fixPath(path)}`.split(/\//).slice(1) - - if (data.getIn) { - return toJS(data.getIn(pathArr, notSetValue)) - } - - return data -} - -/** - * @description Convert parameter under "ordered" path of Immutable Map to a - * Javascript array. This preserves order set by query. - * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) - * @param {String} path - Path of parameter to load - * @param {Object|String|Boolean} notSetValue - Value to return if value is not found - * @return {Object} Data located at path within Immutable Map - * @example Basic - * import { connect } from 'react-redux' - * import { firebaseConnect, orderedToJS } from 'react-redux-firebase' - * - * @firebaseConnect([ - * { - * path: 'todos', - * queryParams: ['orderByChild=text'] // order alphabetically based on text - * }, - * ]) - * @connect(({ firebase }) => ({ - * // this.props.todos loaded from state.firebase.ordered.todos - * todos: orderedToJS(firebase, 'todos') - * }) - */ -export const orderedToJS = (data, path, notSetValue) => { - if (!data) { - return notSetValue - } - - const pathArr = `/ordered${fixPath(path)}`.split(/\//).slice(1) - - if (data.getIn) { - return toJS(data.getIn(pathArr, notSetValue)) - } - - return data -} - /** * @private * @description Build child list based on populate - * @param {Map} data - Immutable Map to be converted to JS object (state.firebase) + * @param {Object} data - Immutable Object to be converted to JS object (state.firebase) * @param {Object} list - Path of parameter to load * @param {Object} populate - Object with population settings */ -export const buildChildList = (data, list, p) => +export const buildChildList = (state, list, p) => mapValues(list, (val, key) => { let getKey = val // Handle key: true lists @@ -245,149 +108,117 @@ export const buildChildList = (data, list, p) => const pathString = p.childParam ? `${p.root}/${getKey}/${p.childParam}` : `${p.root}/${getKey}` + + // console.log('path string:', { pathString, state }) // Set to child under key if populate child exists - if (dataToJS(data, pathString)) { + if (state.data[pathString]) { return p.keyProp - ? { [p.keyProp]: getKey, ...dataToJS(data, pathString) } - : dataToJS(data, pathString) + ? { [p.keyProp]: getKey, ...state.data[pathString] } + : get(state.data, pathString) } // Populate child does not exist return val === true ? val : getKey }) /** - * @description Convert parameter under "data" path of Immutable Map to a + * @description Convert parameter under "data" path of Immutable Object to a * Javascript object with parameters populated based on populates array - * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) + * @param {Object} firebase - Immutable Object to be converted to JS object (state.firebase) * @param {String} path - Path of parameter to load * @param {Array} populates - Array of populate objects * @param {Object|String|Boolean} notSetValue - Value to return if value is not found - * @return {Object} Data located at path within Immutable Map + * @return {Object} Data located at path within Immutable Object * @example Basic * import { connect } from 'react-redux' - * import { firebaseConnect, helpers } from 'react-redux-firebase' - * const { dataToJS } = helpers + * import { firebaseConnect } from 'react-redux-firebase' * const populates = [{ child: 'owner', root: 'users' }] * * const fbWrapped = firebaseConnect([ * { path: '/todos', populates } // load "todos" and matching "users" to redux * ])(App) * - * export default connect(({ firebase }) => ({ + * export default connect((state) => ({ * // this.props.todos loaded from state.firebase.data.todos * // each todo has child 'owner' populated from matching uid in 'users' root - * // for loading un-populated todos use dataToJS(firebase, 'todos') - * todos: populatedDataToJS(firebase, 'todos', populates), + * // for loading un-populated todos use state.firebase.data.todos + * todos: populate(state.firebase, 'todos', populates), * }))(fbWrapped) */ -export const populatedDataToJS = (data, path, populates, notSetValue) => { - if (!data) { - return notSetValue - } +export const populate = (state, path, populates, notSetValue) => { + // TODO: Handle slash and lodash notation // Handle undefined child - if (!dataToJS(data, path, notSetValue)) { - return dataToJS(data, path, notSetValue) + if (!state || !state.data[path]) { + return notSetValue } - const populateObjs = getPopulateObjs(populates) - // reduce array of populates to object of combined populated data - return reduce( - map(populateObjs, (p, obj) => { - // single item with iterable child - if (dataToJS(data, path)[p.child]) { + // test if data is a single object vs a list of objects, try generating + // populates and testing for key presence + const populatesForData = getPopulateObjs(isFunction(populates) + ? populates(last(split(path, '/')), state.data[path]) + : populates) + const dataHasPopluateChilds = every(populatesForData, (populate) => ( + has(state.data[path], populate.child) + )) + if (dataHasPopluateChilds) { + // Data is a single object, resolve populates directly + return reduce( + map(populatesForData, (p, obj) => { // populate child is key - if (isString(dataToJS(data, path)[p.child])) { - const key = dataToJS(data, path)[p.child] + if (isString(get(state.data[path], p.child))) { + const key = get(state.data[path], p.child) const pathString = p.childParam - ? `${p.root}/${key}/${p.childParam}` - : `${p.root}/${key}` - if (dataToJS(data, pathString)) { - return { - [p.child]: p.keyProp - ? { [p.keyProp]: key, ...dataToJS(data, pathString) } - : dataToJS(data, pathString) - } + ? `${p.root}.${key}.${p.childParam}` + : `${p.root}.${key}` + + if (get(state.data, pathString)) { + return set({}, p.child, p.keyProp + ? { [p.keyProp]: key, ...get(state.data, pathString) } + : get(state.data, pathString) + ) } - // matching child does not exist - return dataToJS(data, path) - } - - return { - [p.child]: buildChildList(data, dataToJS(data, path)[p.child], p) + return get(state.data, path) } - } - // list with child param in each item - return mapValues(dataToJS(data, path), (child, i) => { + return set({}, p.child, buildChildList(state, get(state.data[path], p.child), p)) + }), + // combine data from all populates to one object starting with original data + (obj, v) => defaultsDeep(v, obj), state.data[path]) + } else { + // Data is a map of objects, each value has parameters to be populated + return mapValues(state.data[path], (child, childKey) => { + const populatesForDataItem = getPopulateObjs(isFunction(populates) + ? populates(childKey, child) + : populates) + const resolvedPopulates = map(populatesForDataItem, (p, obj) => { // no matching child parameter - if (!child || !child[p.child]) { + if (!child || !get(child, p.child)) { return child } // populate child is key - if (isString(child[p.child])) { - const key = child[p.child] + if (isString(get(child, p.child))) { + const key = get(child, p.child) + // attach child paramter if it exists const pathString = p.childParam - ? `${p.root}/${key}/${p.childParam}` - : `${p.root}/${key}` - if (dataToJS(data, pathString)) { - return { - [p.child]: p.keyProp - ? { [p.keyProp]: key, ...dataToJS(data, pathString) } - : dataToJS(data, pathString) - } + ? `${p.root}.${key}.${p.childParam}` + : `${p.root}.${key}` + if (get(state.data, pathString)) { + return set({}, p.child, (p.keyProp + ? { [p.keyProp]: key, ...get(state.data, pathString) } + : get(state.data, pathString) + )) } // matching child does not exist return child } // populate child list - return { - [p.child]: buildChildList(data, child[p.child], p) - } + return set({}, p.child, buildChildList(state, get(child, p.child), p)) }) - }), - // combine data from all populates to one object starting with original data - (obj, v) => defaultsDeep(v, obj), dataToJS(data, path)) -} - -/** - * @description Load custom object from within store - * @param {Map} firebase - Immutable Map to be converted to JS object (state.firebase) - * @param {String} path - Path of parameter to load - * @param {String} customPath - Part of store from which to load - * @param {Object|String|Boolean} notSetValue - Value to return if value is not found - * @return {Object} Data located at path within state - * @example Basic - * import { connect } from 'react-redux' - * import { firebaseConnect, helpers } from 'react-redux-firebase' - * const { customToJS } = helpers - * - * const fbWrapped = firebaseConnect(['/todos'])(App) - * - * export default connect(({ firebase }) => ({ - * // this.props.todos loaded from state.firebase.data.todos - * requesting: customToJS(firebase, 'todos', 'requesting') - * }))(fbWrapped) - */ -export const customToJS = (data, path, custom, notSetValue) => { - if (!data) { - return notSetValue - } - const pathArr = `/${custom}${fixPath(path)}`.split(/\//).slice(1) - - if (data.getIn) { - return toJS(data.getIn(pathArr, notSetValue)) + // combine data from all populates to one object starting with original data + return reduce( + resolvedPopulates, + (obj, v) => defaultsDeep(v, obj), + child + ) + }) } - - return data -} - -export default { - toJS, - pathToJS, - dataToJS, - orderedToJS, - populatedDataToJS, - customToJS, - isLoaded, - isEmpty } diff --git a/src/reducer.js b/src/reducer.js index 9948de1e3..68d909e05 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,164 +1,174 @@ -import { fromJS } from 'immutable' -import { dropRight } from 'lodash' -import { actionTypes, paramSplitChar } from './constants' +import { combineReducers } from 'redux' +import { set } from 'lodash' +import { actionTypes } from './constants' const { START, SET, + SET_ORDERED, SET_PROFILE, LOGIN, LOGOUT, LOGIN_ERROR, NO_VALUE, - UNSET_LISTENER, + // UNSET_LISTENER, AUTHENTICATION_INIT_STARTED, AUTHENTICATION_INIT_FINISHED, - UNAUTHORIZED_ERROR + UNAUTHORIZED_ERROR, + AUTH_UPDATE_SUCCESS } = actionTypes -const emptyState = { - auth: undefined, - authError: undefined, - profile: undefined, - isInitializing: undefined, - data: {}, - timestamp: {}, - requesting: {}, - requested: {} -} - -const initialState = fromJS(emptyState) - const pathToArr = path => path ? path.split(/\//).filter(p => !!p) : [] /** - * @name firebaseStateReducer - * @description Reducer for react redux firebase. This function is called - * automatically by redux every time an action is fired. Based on which action - * is called and its payload, the reducer will update redux state with relevant - * changes. - * @param {Map} state - Current Redux State - * @param {Object} action - Action which will modify state - * @param {String} action.type - Type of Action being called - * @param {String} action.data - Type of Action which will modify state - * @return {Map} Redux State. + * Reducer for requesting state. Changed by `START` and `SET` actions. + * @param {Object} state - Current requesting redux state + * @param {object} action - Object containing the action that was dispatched + * @return {Object} Profile state after reduction */ -export default (state = initialState, action = {}) => { - const { path, timestamp, requesting, requested } = action - let pathArr - let retVal - +const requestingReducer = (state = {}, action) => { + const { path, requesting } = action switch (action.type) { case START: - pathArr = pathToArr(path) - retVal = (requesting !== undefined) - ? state.setIn(['requesting', pathArr.join(paramSplitChar)], fromJS(requesting)) - : state.deleteIn(['requesting', pathArr.join(paramSplitChar)]) - - retVal = (requested !== undefined) - ? retVal.setIn(['requested', pathArr.join(paramSplitChar)], fromJS(requested)) - : retVal.deleteIn(['requested', pathArr.join(paramSplitChar)]) - - return retVal - case SET: - - const { data, ordered } = action - pathArr = pathToArr(path) - - // Handle invalid keyPath error caused by deep setting to a null value - if (data !== undefined && state.getIn(['data', ...pathArr]) === null) { - retVal = state.deleteIn(['data', ...pathArr]) - } else if (state.getIn(dropRight(['data', ...pathArr])) === null) { - retVal = state.deleteIn(dropRight(['data', ...pathArr])) - } else { - retVal = state // start with state + return { + ...state, + [pathToArr(path).join('/')]: requesting } + // TODO: Handle NO_VALUE case + // case NO_VALUE: + default: + return state + } +} - retVal = (data !== undefined) - ? retVal.setIn(['data', ...pathArr], fromJS(data)) - : retVal.deleteIn(['data', ...pathArr]) - - retVal = (ordered !== undefined) - ? retVal.setIn(['ordered', ...pathArr], fromJS(ordered)) - : retVal.deleteIn(['ordered', ...pathArr]) - - retVal = (timestamp !== undefined) - ? retVal.setIn(['timestamp', pathArr.join(paramSplitChar)], fromJS(timestamp)) - : retVal.deleteIn(['timestamp', pathArr.join(paramSplitChar)]) - - retVal = (requesting !== undefined) - ? retVal.setIn(['requesting', pathArr.join(paramSplitChar)], fromJS(requesting)) - : retVal.deleteIn(['requesting', pathArr.join(paramSplitChar)]) - - retVal = (requested !== undefined) - ? retVal.setIn(['requested', pathArr.join(paramSplitChar)], fromJS(requested)) - : retVal.deleteIn(['requested', pathArr.join(paramSplitChar)]) - - return retVal +const getPathStr = (path) => path ? path.replace('/', '.') : '' +/** + * Reducer for data state. Changed by `LOGIN`, `LOGOUT`, and `LOGIN_ERROR` + * actions. + * @param {Object} state - Current data redux state + * @param {object} action - Object containing the action that was dispatched + * @return {Object} Profile state after reduction + */ +const dataReducer = (state = {}, action) => { + const { path, data, ordered } = action + switch (action.type) { + case SET: + return { + ...state, + ...set({}, getPathStr(path), data) + } + case SET_ORDERED: + return { + ...state, + ...set({}, getPathStr(path), ordered) + } case NO_VALUE: - pathArr = pathToArr(path) - retVal = state.setIn(['data', ...pathArr], fromJS({})) - - retVal = (timestamp !== undefined) - ? retVal.setIn(['timestamp', pathArr.join(paramSplitChar)], fromJS(timestamp)) - : retVal.deleteIn(['timestamp', pathArr.join(paramSplitChar)]) - - retVal = (requesting !== undefined) - ? retVal.setIn(['requesting', pathArr.join(paramSplitChar)], fromJS(requesting)) - : retVal.deleteIn(['requesting', pathArr.join(paramSplitChar)]) - - retVal = (requested !== undefined) - ? retVal.setIn(['requested', pathArr.join(paramSplitChar)], fromJS(requested)) - : retVal.deleteIn(['requested', pathArr.join(paramSplitChar)]) - - return retVal - - case UNSET_LISTENER: - pathArr = pathToArr(path) - retVal = state.deleteIn(['data', ...pathArr]) - retVal = retVal.deleteIn(['timestamp', pathArr.join(paramSplitChar)]) - retVal = retVal.deleteIn(['requesting', pathArr.join(paramSplitChar)]) - retVal = retVal.deleteIn(['requested', pathArr.join(paramSplitChar)]) + return { + ...state, + ...set({}, getPathStr(path), {}) + } + default: + return state + } +} - return retVal +/** + * Reducer for auth state. Changed by `LOGIN`, `LOGOUT`, and `LOGIN_ERROR` + * actions. + * @param {Object} state - Current auth redux state + * @param {object} action - Object containing the action that was dispatched + * @return {Object} Profile state after reduction + */ +const authReducer = (state = {}, action) => { + switch (action.type) { + case LOGIN: + case AUTH_UPDATE_SUCCESS: + return action.auth || undefined + case LOGOUT: + case LOGIN_ERROR: + return null + default: + return state + } +} +/** + * Reducer for profile state. Changed by `SET_PROFILE`, `LOGOUT`, and + * `LOGIN_ERROR` actions. + * @param {Object} state - Current profile redux state + * @param {object} action - Object containing the action that was dispatched + * @return {Object} Profile state after reduction + */ +const profileReducer = (state = {}, action) => { + switch (action.type) { case SET_PROFILE: - return (action.profile !== undefined) - ? state.setIn(['profile'], fromJS(action.profile)) - : state.deleteIn(['profile']) - + return { + ...state, + ...action.profile + } case LOGOUT: - return fromJS({ - auth: null, - authError: null, - profile: null, - isInitializing: false, - data: {} - }) - - case LOGIN: - return state.setIn(['auth'], fromJS(action.auth)) - .setIn(['authError'], null) - case LOGIN_ERROR: + return null + default: return state - .setIn(['authError'], action.authError) - .setIn(['auth'], null) - .setIn(['profile'], null) + } +} +/** + * Reducer for isInitializing state. Changed by `AUTHENTICATION_INIT_STARTED` + * and `AUTHENTICATION_INIT_FINISHED` actions. + * @param {Object} state - Current isInitializing redux state + * @param {object} action - Object containing the action that was dispatched + * @return {Object} Profile state after reduction + */ +const isInitializingReducer = (state = false, action) => { + switch (action.type) { case AUTHENTICATION_INIT_STARTED: - return initialState.setIn(['isInitializing'], true) - // return state.setIn(['isInitializing'], true) // throws state.setIn not a function error - + return true case AUTHENTICATION_INIT_FINISHED: - return state.setIn(['isInitializing'], false) + return false + default: + return state + } +} +/** + * Reducer for errors state. Changed by `UNAUTHORIZED_ERROR` + * and `LOGOUT` actions. + * @param {Object} state - Current authError redux state + * @param {object} action - Object containing the action that was dispatched + * @return {Object} Profile state after reduction + */ +const errorsReducer = (state = [], action) => { + switch (action.type) { case UNAUTHORIZED_ERROR: - return state.setIn(['authError'], action.authError) - + return [...state, action.payload] + case LOGOUT: + return null default: return state } } + +/** + * @name firebaseStateReducer + * @description Reducer for react redux firebase. This function is called + * automatically by redux every time an action is fired. Based on which action + * is called and its payload, the reducer will update redux state with relevant + * changes. + * @param {Map} state - Current Redux State + * @param {Object} action - Action which will modify state + * @param {String} action.type - Type of Action being called + * @param {String} action.data - Type of Action which will modify state + * @return {Map} State + */ +export default combineReducers({ + requesting: requestingReducer, + data: dataReducer, + auth: authReducer, + profile: profileReducer, + isInitializing: isInitializingReducer, + errors: errorsReducer +}) diff --git a/src/utils/actions.js b/src/utils/actions.js index 151d0e53e..bdbc4c260 100644 --- a/src/utils/actions.js +++ b/src/utils/actions.js @@ -1,3 +1,5 @@ +import { isObject } from 'lodash' + /** * @description Wrap method call in dispatched actions * @param {Function} dispatch - Action dispatch function @@ -9,7 +11,8 @@ */ export const wrapInDispatch = (dispatch, { method, args, types }) => { dispatch({ - type: types[0] + type: isObject(types[0]) ? types[0].type : types[0], + payload: isObject(types[0]) ? types[0].payload : { args } }) return method(...args) .then((val) => { @@ -17,11 +20,13 @@ export const wrapInDispatch = (dispatch, { method, args, types }) => { type: types[1], payload: val }) + return val }) .catch((err) => { dispatch({ type: types[2], payload: err }) + return Promise.reject(err) }) } diff --git a/src/utils/populate.js b/src/utils/populate.js index ef9913e19..7a61c9010 100644 --- a/src/utils/populate.js +++ b/src/utils/populate.js @@ -2,12 +2,14 @@ import { filter, isString, isArray, + isFunction, isObject, map, get, forEach, set, - has + has, + every } from 'lodash' /** @@ -127,23 +129,30 @@ export const populateList = (firebase, list, p, results) => { * @param {Object} originalObj - Object to have parameter populated * @param {Object} populateString - String containg population data */ -export const promisesForPopulate = (firebase, originalData, populatesIn) => { +export const promisesForPopulate = (firebase, dataKey, originalData, populatesIn) => { // TODO: Handle selecting of parameter to populate with (i.e. displayName of users/user) let promisesArray = [] let results = {} - const populates = getPopulateObjs(populatesIn) - // Loop over all populates - forEach(populates, (p) => { - // Data is single parameter - if (has(originalData, p.child)) { - // Single Parameter is single ID - if (isString(originalData[p.child])) { + + // test if data is a single object, try generating populates and looking for the child + const populatesForData = getPopulateObj(isFunction(populatesIn) + ? populatesIn(dataKey, originalData) + : populatesIn) + + const dataHasPopulateChilds = every(populatesForData, (populate) => ( + has(originalData, populate.child) + )) + + if (dataHasPopulateChilds) { + // Data is a single object, resolve populates directly + forEach(populatesForData, (p) => { + if (isString(get(originalData, p.child))) { return promisesArray.push( - getPopulateChild(firebase, p, originalData[p.child]) + getPopulateChild(firebase, p, get(originalData, p.child)) .then((v) => { // write child to result object under root name if it is found if (v) { - set(results, `${p.root}.${originalData[p.child]}`, v) + set(results, `${p.root}.${get(originalData, p.child)}`, v) } }) ) @@ -151,43 +160,51 @@ export const promisesForPopulate = (firebase, originalData, populatesIn) => { // Single Parameter is list return promisesArray.push( - populateList(firebase, originalData[p.child], p, results) + populateList(firebase, get(originalData, p.child), p, results) ) - } - - // Data is list, each item has parameter to be populated + }) + } else { + // Data is a map of objects, each value has parameters to be populated forEach(originalData, (d, key) => { - // Get value of parameter to be populated (key or list of keys) - const idOrList = get(d, p.child) + // generate populates for this data item if a fn was passed + const populatesForDataItem = getPopulateObj(isFunction(populatesIn) + ? populatesIn(key, d) + : populatesIn) - // Parameter/child of list item does not exist - if (!idOrList) { - return - } + // resolve each populate for this data item + forEach(populatesForDataItem, (p) => { + // get value of parameter to be populated (key or list of keys) + const idOrList = get(d, p.child) - // Parameter of each list item is single ID - if (isString(idOrList)) { - return promisesArray.push( - getPopulateChild(firebase, p, idOrList) - .then((v) => { - // write child to result object under root name if it is found - if (v) { - set(results, `${p.root}.${idOrList}`, v) - } - return results - }) - ) - } + // Parameter/child of list item does not exist + if (!idOrList) { + return + } - // Parameter of each list item is a list of ids - if (isArray(idOrList) || isObject(idOrList)) { - // Create single promise that includes a promise for each child - return promisesArray.push( - populateList(firebase, idOrList, p, results) - ) - } + // Parameter of each list item is single ID + if (isString(idOrList)) { + return promisesArray.push( + getPopulateChild(firebase, p, idOrList) + .then((v) => { + // write child to result object under root name if it is found + if (v) { + set(results, `${p.root}.${idOrList}`, v) + } + return results + }) + ) + } + + // Parameter of each list item is a list of ids + if (isArray(idOrList) || isObject(idOrList)) { + // Create single promise that includes a promise for each child + return promisesArray.push( + populateList(firebase, idOrList, p, results) + ) + } + }) }) - }) + } // Return original data after population promises run return Promise.all(promisesArray).then(() => results) diff --git a/src/utils/query.js b/src/utils/query.js index 9d2f44887..94c84422e 100644 --- a/src/utils/query.js +++ b/src/utils/query.js @@ -1,4 +1,5 @@ import { actionTypes } from '../constants' +import { isFunction } from 'lodash' const { UNSET_LISTENER } = actionTypes @@ -95,16 +96,21 @@ export const getWatcherCount = (firebase, event, path, queryId = undefined) => { export const unsetWatcher = (firebase, dispatch, event, path, queryId = undefined) => { let id = queryId || getQueryIdFromPath(path, event) || getWatchPath(event, path) path = path.split('#')[0] - if (firebase._.watchers[id] <= 1) { - delete firebase._.watchers[id] + const { watchers, config } = firebase._ + if (watchers[id] <= 1) { + delete watchers[id] if (event !== 'first_child' && event !== 'once') { firebase.database().ref().child(path).off(event) - if (firebase._.config.distpatchOnUnsetListener) { + // TODO: Remove config.distpatchOnUnsetListener + if (config.dispatchOnUnsetListener || config.distpatchOnUnsetListener) { + if (config.distpatchOnUnsetListener && isFunction(console.warn)) { // eslint-disable-line no-console + console.warn('config.distpatchOnUnsetListener is Depreceated and will be removed in future versions. Please use config.dispatchOnUnsetListener (dispatch spelled correctly).') // eslint-disable-line no-console + } dispatch({ type: UNSET_LISTENER, path }) } } - } else if (firebase._.watchers[id]) { - firebase._.watchers[id]-- + } else if (watchers[id]) { + watchers[id]-- } } diff --git a/src/utils/storage.js b/src/utils/storage.js index ca63a4e3d..4a8072a1b 100644 --- a/src/utils/storage.js +++ b/src/utils/storage.js @@ -4,7 +4,7 @@ export const deleteFile = (firebase, { path, dbPath }) => .delete() .then(() => !dbPath - ? ({ path, dbPath }) + ? ({ path }) : firebase // Handle option for removing file info from database .database() .ref(dbPath) diff --git a/tests/setup.js b/tests/setup.js index 83e4e54e9..fe7097b64 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -6,6 +6,15 @@ var sinon = require('sinon') var chaiAsPromised = require('chai-as-promised') var sinonChai = require('sinon-chai') var jsdom = require('jsdom').jsdom +var FirebaseServer = require('firebase-server') + +new FirebaseServer(5000, 'localhost.firebaseio.test', { // eslint-disable-line no-new + users: { + Iq5b0qK2NtgggT6U3bU6iZRGyma2: { + displayName: 'Tester' + } + } +}) // Chai Plugins chai.use(chaiAsPromised) @@ -21,15 +30,17 @@ global.navigator = global.window.navigator // needed to fix "Error: The XMLHttpRequest compatibility library was not found." from Firebase auth global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest +// needed to fix: "FIREBASE WARNING: wss:// URL used, but browser isn't known to support websockets. Trying anyway." +global.WebSocket = require('ws') -// Firebase var Firebase = global.Firebase = require('firebase') + var fbConfig = global.fbConfig = { - apiKey: 'AIzaSyA-4ZcRO93M3bl6SdUjTiqN7tNfNOjV6D4', - authDomain: 'tester-2d4fa.firebaseapp.com', - databaseURL: 'https://tester-2d4fa.firebaseio.com', - storageBucket: 'tester-2d4fa.appspot.com', - messagingSenderId: '553568276840' + apiKey: 'asdf', // placeholder + authDomain: 'asdf', // placeholder + databaseURL: 'ws://127.0.1:5000', + storageBucket: 'asdf', // placeholder + messagingSenderId: 'asdf' // placeholder } // Swallow firebase reinitialize error (useful when using watch) diff --git a/tests/unit/actions/auth.spec.js b/tests/unit/actions/auth.spec.js index 517872404..94da7676e 100644 --- a/tests/unit/actions/auth.spec.js +++ b/tests/unit/actions/auth.spec.js @@ -1,4 +1,3 @@ -/* global describe expect it beforeEach */ import { dispatchLoginError, dispatchUnauthorizedError, @@ -167,7 +166,7 @@ describe('Actions: Auth', () => { describe('createUserProfile', () => { it('creates profile if config is enabled', () => { - return createUserProfile(dispatch, firebase, { uid: '123', email: 'test@test.com', providerData: [{}] }, { some: 'asdf' }) + return createUserProfile(dispatch, Firebase, { uid: '123', email: 'test@test.com', providerData: [{}] }, { some: 'asdf' }) .then((profile) => { expect(profile).to.be.an.object }) @@ -176,13 +175,13 @@ describe('Actions: Auth', () => { describe('login', () => { it('handles invalid email login', () => - login(dispatch, firebase, fakeLogin) + login(dispatch, fakeFirebase, fakeLogin) .catch((err) => { expect(err.code).to.equal('auth/user-not-found') }) , 4000) it('handles invalid token login', () => - login(dispatch, firebase, { token: 'test@tst.com' }) + login(dispatch, fakeFirebase, { token: 'test@tst.com' }) .catch((err) => { expect(err.code).to.equal('auth/invalid-custom-token') }) @@ -327,7 +326,6 @@ describe('Actions: Auth', () => { it('auth/weak-password', () => { return confirmPasswordReset(dispatch, fakeFirebase, 'auth/weak-password', 'error') .catch((err) => { - console.log('error:', err) expect(err.code).to.be.a.string }) }) diff --git a/tests/unit/actions/query.spec.js b/tests/unit/actions/query.spec.js index e31a60bf4..f8d11ac5b 100644 --- a/tests/unit/actions/query.spec.js +++ b/tests/unit/actions/query.spec.js @@ -1,4 +1,3 @@ -/* global describe expect it beforeEach */ import queryAction from '../../../src/actions/query' import { watchEvent, @@ -49,7 +48,7 @@ describe('Actions: Query', () => { expect(unWatchEvent).to.be.a.function }) it('runs given basic params', () => { - expect(unWatchEvent(firebase, dispatch, 'once', 'projects')).to.be.a.function + expect(unWatchEvent(firebase, dispatch, { type: 'once', path: 'projects' })).to.be.a.function }) }) diff --git a/tests/unit/actions/storage.js b/tests/unit/actions/storage.spec.js similarity index 81% rename from tests/unit/actions/storage.js rename to tests/unit/actions/storage.spec.js index 541949f06..e365b9f34 100644 --- a/tests/unit/actions/storage.js +++ b/tests/unit/actions/storage.spec.js @@ -1,4 +1,4 @@ -/* global describe expect it beforeEach sinon */ +/* eslint-disable no-unused-expressions */ import { uploadFileWithProgress, uploadFile, @@ -23,7 +23,8 @@ const fakeFirebase = { on: () => Promise.resolve({ val: () => ({ some: 'obj' }) }), off: () => Promise.resolve({ val: () => ({ some: 'obj' }) }), once: () => Promise.resolve({ val: () => ({ some: 'obj' }) }) - }) + }), + remove: () => Promise.resolve({ val: () => ({ some: 'obj' }) }) }) }), storage: () => ({ @@ -94,11 +95,20 @@ describe('Actions: Storage', () => { it('is exported', () => { expect(deleteFile).to.be.a.function }) - it('runs given basic params', () => - deleteFile(dispatch, fakeFirebase, { path: 'projects', file: { name: 'test.png' } }) - .then((snap) => { - expect(snap).to.be.an.object - }) + + it('runs given path', () => + expect(deleteFile(dispatch, fakeFirebase, { path: 'projects' })) + .to + .eventually + .become({path: 'projects'}) ) + + it('runs given basic params', () => { + const metaObj = { path: 'projects/test.png', dbPath: 'test.png' } + return expect(deleteFile(dispatch, fakeFirebase, metaObj)) + .to + .eventually + .become(metaObj) + }) }) }) diff --git a/tests/unit/compose.spec.js b/tests/unit/compose.spec.js index ef4337144..8206a062e 100644 --- a/tests/unit/compose.spec.js +++ b/tests/unit/compose.spec.js @@ -1,9 +1,10 @@ -/* global describe expect it */ import { omit } from 'lodash' import { createStore, combineReducers, compose } from 'redux' -import composeFunc, { getFirebase } from '../../src/compose' +import composeFunc from '../../src/compose' + const exampleData = { data: { some: 'data' } } const reducer = sinon.spy() + const generateCreateStore = (params) => compose(composeFunc( params ? omit(fbConfig, params) : fbConfig, @@ -99,7 +100,7 @@ describe('Compose', () => { ) it('has on err onComplete', () => { const func = sinon.spy() - helpers.uniqueSet('test', {some: 'asdf'}, func) + return helpers.uniqueSet('test', {some: 'asdf'}, func) .catch((err) => { expect(func).to.have.been.calledOnce }) @@ -119,7 +120,6 @@ describe('Compose', () => { describe.skip('unWatchEvent', () => { it.skip('unWatchesEvent', () => helpers.unWatchEvent('value', 'test') - ) }) @@ -155,6 +155,44 @@ describe('Compose', () => { } }) + describe('updateProfile', () => { + it('acccepts an object', () => + expect(helpers.updateProfile({ displayName: 'test' })).to.eventually.become(undefined) + ) + }) + + describe('updateAuth', () => { + it('rejects when not authenticated', () => + expect(helpers.updateAuth()).to.be.rejectedWith('User must be logged in to update auth.') + ) + + // TODO: test that update auth when authenticated + it.skip('updates auth object if authenticated', () => + expect(helpers.updateAuth()).to.eventually.become(undefined) + ) + + // TODO: test that updateProfile is called if updateInProfile is true + it.skip('calls update profile if updateInProfile is true', () => + expect(helpers.updateAuth({}, true)).to.eventually.become(undefined) + ) + }) + + describe('updateEmail', () => { + it('rejects when not authenticated', () => + expect(helpers.updateEmail()).to.be.rejectedWith('User must be logged in to update email.') + ) + + // TODO: test that update auth when authenticated + it.skip('updates auth object if authenticated', () => + expect(helpers.updateEmail()).to.eventually.become(undefined) + ) + + // TODO: test that updateProfile is called if updateInProfile is true + it.skip('calls update profile if updateInProfile is true', () => + expect(helpers.updateEmail({}, true)).to.eventually.become(undefined) + ) + }) + describe('storage', () => { try { helpers.storage() @@ -180,7 +218,7 @@ describe('Compose', () => { }) }) - describe('getFirebase', () => { + describe.skip('getFirebase', () => { it('exports firebase instance', () => { expect(getFirebase()).to.be.an.object }) diff --git a/tests/unit/connect.spec.js b/tests/unit/connect.spec.js index 86d1234e7..1b6008108 100644 --- a/tests/unit/connect.spec.js +++ b/tests/unit/connect.spec.js @@ -1,9 +1,10 @@ -import React, { createClass, Children, Component, PropTypes } from 'react' +import React, { createClass, Children, Component } from 'react' +import PropTypes from 'prop-types' import ReactDOM from 'react-dom' -import connect from '../../src/connect' -import reactReduxFirebase from '../../src/compose' import TestUtils from 'react-addons-test-utils' import { createStore, compose, combineReducers } from 'redux' +import connect from '../../src/connect' +import reactReduxFirebase from '../../src/compose' describe('Connect', () => { class Passthrough extends Component { diff --git a/tests/unit/helpers.spec.js b/tests/unit/helpers.spec.js index 71d5911d3..067472b97 100644 --- a/tests/unit/helpers.spec.js +++ b/tests/unit/helpers.spec.js @@ -1,6 +1,7 @@ -/* global describe expect it */ import { fromJS } from 'immutable' -import helpers from '../../src/helpers' +import * as helpers from '../../src/helpers' +import { buildChildList } from '../../src/helpers' + const exampleData = { data: { some: 'data', @@ -27,6 +28,19 @@ const exampleData = { ABC: true, abc: true } + }, + QRS: { + owner: 'ABC', + nested: { + owner: 'ABC' + }, + notes: { + 123: true, + }, + collaborators: { + ABC: true, + abc: true + } } }, users: { @@ -40,104 +54,72 @@ const exampleData = { } } }, + ordered: { + projects: [ + { + owner: 'ABC', + notes: { + 123: true, + }, + collaborators: { + ABC: true, + abc: true + } + }, + ] + }, timestamp: { 'some/path': { test: 'key' } }, snapshot: { some: 'snapshot' } } const exampleState = fromJS(exampleData) describe('Helpers:', () => { - describe('toJS', () => { - it('exists', () => { - expect(helpers).to.respondTo('toJS') - }) - - it('handles non-immutable data', () => { - expect(helpers.toJS(exampleData)).to.equal(exampleData) - }) - - it('handles immutable data', () => { - expect(helpers.toJS(exampleState)).to.be.an.object - }) - }) - - describe('pathToJS', () => { - it('exists', () => { - expect(helpers).to.respondTo('pathToJS') - }) - - it('passes notSetValue', () => { - expect(helpers.pathToJS(null, '/some', exampleData)) - .to - .equal(exampleData) - }) - - it('gets data', () => { - expect(helpers.pathToJS(exampleState, '/some', exampleData)) - .to - .equal(exampleData) - }) - - it('gets meta (string key)', () => { - expect(helpers.pathToJS(exampleState, 'timestamp/some/path')) - .to - .have - .keys('test') - }) - - it('returns state if its not an immutable Map', () => { - const fakeState = {} - expect(helpers.pathToJS(fakeState, 'asdf')) - .to - .equal(fakeState) - }) - }) - - describe('dataToJS', () => { + describe.skip('ordered', () => { it('exists', () => { - expect(helpers).to.respondTo('dataToJS') + expect(helpers).to.respondTo('ordered') }) it('passes notSetValue', () => { - expect(helpers.dataToJS(null, '/some', exampleData)) + expect(helpers.ordered(null, '/some', exampleData)) .to .equal(exampleData) }) it('gets data from state', () => { - const path = 'some' - expect(helpers.dataToJS(exampleState, path, exampleData)) + const path = 'projects' + expect(JSON.stringify(helpers.ordered(exampleState, path, exampleData))) .to - .equal(exampleData.data[path]) + .equal(JSON.stringify(exampleData.ordered[path])) }) it('returns state if its not an immutable Map', () => { const fakeState = { } - expect(helpers.dataToJS(fakeState, 'asdf')) + expect(helpers.ordered(fakeState, 'asdf')) .to .equal(fakeState) }) }) - describe('populatedDataToJS', () => { + describe('populate', () => { it('exists', () => { - expect(helpers).to.respondTo('populatedDataToJS') + expect(helpers).to.respondTo('populate') }) it('passes notSetValue', () => { - expect(helpers.populatedDataToJS(null, '/some', [], exampleData)) + expect(helpers.populate(null, '/some', [], exampleData)) .to .equal(exampleData) }) it('returns undefined for non existant path', () => { - expect(helpers.populatedDataToJS(exampleState, '/asdfasdfadsf', [])) + expect(helpers.populate(exampleState, '/asdfasdfadsf', [])) .to .equal(undefined) }) it('returns unpopulated data for no populates', () => { const path = 'projects' - expect(helpers.populatedDataToJS(exampleState, path, []).CDF.owner) + expect(helpers.populate(exampleState, path, []).CDF.owner) .to .equal(exampleData.data[path].CDF.owner) }) @@ -147,16 +129,25 @@ describe('Helpers:', () => { it('populates value', () => { const path = 'projects/CDF' const rootName = 'users' - expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner) + expect(helpers.populate(exampleState, path, [{ child: 'owner', root: rootName }]).owner) + .to + .have + .property('displayName', 'scott') + }) + it('handles child path', () => { + const path = 'projects/QRS' + const rootName = 'users' + const populates = [{ child: 'nested.owner', root: rootName }] + const populatedData = helpers.populate(exampleState, path, populates) + expect(populatedData.nested.owner) .to .have .property('displayName', 'scott') }) - it('populates childParam', () => { const path = 'projects/CDF' const rootName = 'users' - expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }]).owner) + expect(helpers.populate(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }]).owner) .to .have .equal('scott') @@ -164,7 +155,7 @@ describe('Helpers:', () => { it('keeps non-existant children', () => { const path = 'projects/OKF' const rootName = 'users' - expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }]).owner) + expect(helpers.populate(exampleState, path, [{ child: 'owner', root: rootName }]).owner) .to .have .equal('asdfasdf') @@ -177,7 +168,7 @@ describe('Helpers:', () => { const populates = [ { child: 'collaborators', root: rootName }, ] - const populatedData = helpers.populatedDataToJS(exampleState, path, populates) + const populatedData = helpers.populate(exampleState, path, populates) expect(populatedData) .to .have @@ -186,6 +177,22 @@ describe('Helpers:', () => { }) }) + describe('config as function', () => { + it('populates values', () => { + const path = 'projects/CDF' + const rootName = 'users' + const populates = (projectKey, projectData) => ([ + // configure populates with key / data tuple... + { child: 'owner', root: rootName } + ]) + const populatedData = helpers.populate(exampleState, path, populates) + expect(populatedData.owner) + .to + .have + .property('displayName', 'scott') + }) + }) + }) describe('list', () => { @@ -195,7 +202,7 @@ describe('Helpers:', () => { const path = 'projects' const rootName = 'users' const valName = 'CDF' - expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName }])[valName].owner) + expect(helpers.populate(exampleState, path, [{ child: 'owner', root: rootName }])[valName].owner) .to .have .property('displayName', 'scott') @@ -205,7 +212,7 @@ describe('Helpers:', () => { const path = 'projects' const rootName = 'users' const valName = 'CDF' - expect(helpers.populatedDataToJS(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }])[valName].owner) + expect(helpers.populate(exampleState, path, [{ child: 'owner', root: rootName, childParam: 'displayName' }])[valName].owner) .to .have .equal('scott') @@ -220,7 +227,7 @@ describe('Helpers:', () => { const populates = [ { child: 'collaborators', root: rootName }, ] - const populatedData = helpers.populatedDataToJS(exampleState, path, populates) + const populatedData = helpers.populate(exampleState, path, populates) expect(populatedData) .to .have @@ -235,12 +242,12 @@ describe('Helpers:', () => { const populates = [ { child: 'collaborators', root: rootName }, ] - expect(helpers.populatedDataToJS(exampleState, path, populates)) + expect(helpers.populate(exampleState, path, populates)) .to .have .deep .property(`${valName}.collaborators.abc`, true) - expect(helpers.populatedDataToJS(exampleState, path, populates)) + expect(helpers.populate(exampleState, path, populates)) .to .have .deep @@ -259,13 +266,13 @@ describe('Helpers:', () => { { child: 'notes', root: 'notes' }, ] // check that notes are populated - expect(helpers.populatedDataToJS(exampleState, `/${path}`, populates)) + expect(helpers.populate(exampleState, `/${path}`, populates)) .to .have .deep .property(`${valName}.notes.123.text`, exampleData.data.notes['123'].text) // check that owner is populated - expect(helpers.populatedDataToJS(exampleState, `/${path}`, populates)) + expect(helpers.populate(exampleState, `/${path}`, populates)) .to .have .deep @@ -282,12 +289,12 @@ describe('Helpers:', () => { { child: 'collaborators', root: rootName }, ] // TODO: Test both children are populated - expect(helpers.populatedDataToJS(exampleState, `/${path}`, populates)) + expect(helpers.populate(exampleState, `/${path}`, populates)) .to .have .deep .property(`${valName}.owner.displayName`, exampleData.data[rootName].ABC.displayName) - expect(helpers.populatedDataToJS(exampleState, `/${path}`, populates)) + expect(helpers.populate(exampleState, `/${path}`, populates)) .to .have .deep @@ -298,30 +305,6 @@ describe('Helpers:', () => { }) - describe('customToJS', () => { - it('exists', () => { - expect(helpers).to.respondTo('customToJS') - }) - - it('handles non-immutable state', () => { - expect(helpers.customToJS(exampleData, '/some', 'some')) - .to - .equal(exampleData) - }) - - it('passes notSetValue', () => { - expect(helpers.customToJS(null, '/some', 'some', exampleData)) - .to - .equal(exampleData) - }) - - it('passes custom data', () => { - expect(helpers.customToJS(exampleState, '/some', 'snapshot')) - .to - .exist - }) - }) - describe('isLoaded', () => { it('exists', () => { expect(helpers).to.respondTo('isLoaded') diff --git a/tests/unit/reducer.spec.js b/tests/unit/reducer.spec.js index c32868712..c0fd9f30a 100644 --- a/tests/unit/reducer.spec.js +++ b/tests/unit/reducer.spec.js @@ -1,5 +1,3 @@ -/* global describe expect it */ -import { fromJS } from 'immutable' import { firebaseStateReducer } from '../../src' import { actionTypes } from '../../src/constants' const emptyState = { @@ -24,8 +22,8 @@ const noError = { authError: null } const noAuth = { auth: null, profile: null } const exampleData = { some: 'data' } const externalState = { data: { asdfasdf: {} } } -const exampleState = fromJS({}) -const exampleEmptyState = fromJS(emptyState) +const exampleState = {} +const exampleEmptyState = emptyState describe('reducer', () => { it('is a function', () => { @@ -33,46 +31,77 @@ describe('reducer', () => { }) it('handles no initialState', () => { - expect( - JSON.stringify(firebaseStateReducer(undefined, {}).toJS())) - .to.equal(JSON.stringify(initialState)) + expect(firebaseStateReducer(undefined, {})).to.equal(initialState) }) it('returns state by default', () => { - expect(firebaseStateReducer(exampleData)) - .to.equal(exampleData) + expect(firebaseStateReducer(exampleData)).to.equal(exampleData) }) describe('SET action', () => { it('deletes data from state when data is null', () => { expect( - firebaseStateReducer( - exampleState, + firebaseStateReducer(exampleState, { type: actionTypes.SET, path: 'test' } ) ).to.equal(exampleState) }) + it('sets state', () => { const path = 'test' const pathArray = path.split(/\//).filter(p => !!p) - console.log('path:', { type: actionTypes.SET, path, data: {} }) expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.SET, path, data: {} } - )) - ).to.equal(JSON.stringify(exampleState.setIn(['data', ...pathArray], fromJS({})))) + ) + ).to.equal(exampleState.setIn(['data', ...pathArray], {})) + }) + it('handles already existing parent that is null', () => { + const childPath = 123 + const path = `test/${childPath}` + const pathArray = path.split(/\//).filter(p => !!p) + const newData = { some: 'val' } + expect( + firebaseStateReducer( + {data: { test: null } }, + { type: actionTypes.SET, path, data: newData } + ) + ).to.equal(exampleState.data,newData) + }) + + it('handles already existing value of null', () => { + const path = 'test/123' + const pathArray = path.split(/\//).filter(p => !!p) + const newData = { some: 'val' } + expect( + firebaseStateReducer( + { data: { test: { '123': null } } }, + { type: actionTypes.SET, path, data: newData } + ) + ).to.equal(exampleState.data[pathArray],newData) }) }) describe('NO_VALUE action', () => { it('sets state', () => { expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.NO_VALUE, path: 'asdfasdf' } - ).toJS()) - ).to.equal(JSON.stringify(externalState)) + ) + ).to.equal(externalState) + }) + }) + + describe('UNSET_LISTENER action', () => { + it('sets state', () => { + expect( + firebaseStateReducer( + exampleState, + { type: actionTypes.UNSET_LISTENER, path: 'asdfasdf' } + ) + ).to.equal({}) }) }) @@ -80,91 +109,87 @@ describe('reducer', () => { it('sets state', () => { const profile = { email: 'test@test.com' } expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.SET_PROFILE, profile } - ).toJS()) - ).to.equal(JSON.stringify({ profile })) + ) + ).to.equal({ profile }) }) it('removes for no profile', () => { const profile = { email: 'test@test.com' } expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.SET_PROFILE } - ).toJS()) - ).to.equal(JSON.stringify(exampleState.deleteIn(['profile']))) + ) + ).to.equal(exampleState.deleteIn(['profile'])) }) }) describe('LOGOUT action', () => { it('sets state', () => { expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.LOGOUT } - ).toJS()) - ).to.equal(JSON.stringify({ + ) + ).to.equal({ auth: null, authError: null, profile: null, isInitializing: false, data: {} - })) + }) }) }) describe('LOGIN action', () => { it('sets state', () => { expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.LOGIN } - ).toJS()) - ).to.equal(JSON.stringify(noError)) + ) + ).to.equal(noError) }) }) describe('LOGIN_ERROR action', () => { it('sets state', () => { expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.LOGIN_ERROR } - ).toJS()) - ).to.equal(JSON.stringify(noAuth)) + ) + ).to.equal(noAuth) }) }) describe('AUTHENTICATION_INIT_STARTED action', () => { it('sets state', () => { expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.AUTHENTICATION_INIT_STARTED } - ).toJS()) - ).to.equal(JSON.stringify({ + ) + ).to.equal({ isInitializing: true, data: {}, timestamp: {}, requesting: {}, requested: {} - })) + }) }) }) describe('AUTHENTICATION_INIT_FINISHED action', () => { it('sets state', () => { expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.AUTHENTICATION_INIT_FINISHED } - ).toJS()) - ).to.equal( - JSON.stringify( - exampleState.setIn(['isInitializing'], false).toJS() ) - ) + ).to.equal(exampleState, false) }) }) @@ -172,15 +197,23 @@ describe('reducer', () => { it('sets state', () => { const authError = {} expect( - JSON.stringify(firebaseStateReducer( + firebaseStateReducer( exampleState, { type: actionTypes.UNAUTHORIZED_ERROR, authError } - ).toJS()) - ).to.equal( - JSON.stringify( - exampleState.setIn(['authError'], authError).toJS() ) - ) + ).to.equal(exampleState, authError) + }) + }) + + describe('AUTH_UPDATE_SUCCESS action', () => { + it('sets state', () => { + const authUpdate = { email: 'newEmail' } + expect( + firebaseStateReducer( + exampleState, + { type: actionTypes.AUTH_UPDATE_SUCCESS, payload: authUpdate } + ) + ).to.equal(exampleState, authUpdate) }) }) }) diff --git a/tests/unit/utils/auth.spec.js b/tests/unit/utils/auth.spec.js index 95a90471c..17d65d905 100644 --- a/tests/unit/utils/auth.spec.js +++ b/tests/unit/utils/auth.spec.js @@ -1,4 +1,3 @@ -/* global firebase describe expect it */ import { createAuthProvider, getLoginMethodAndParams @@ -15,6 +14,11 @@ describe('Utils: Auth', () => { expect(createAuthProvider(firebase, 'google', 'email')) .to.be.a.function }) + it('handles customAuthParameters config option', () => { + firebase._.config.customAuthParameters = { google: [{prompt: 'select_account'}] } + expect(createAuthProvider(firebase, 'google', 'email')) + .to.be.a.function + }) it('throws for invalid provider', () => { const provider = 'asdf' expect(() => createAuthProvider(firebase, provider, ['email'])) diff --git a/tests/unit/utils/populate.spec.js b/tests/unit/utils/populate.spec.js index 68fb376e1..76faa8dd2 100644 --- a/tests/unit/utils/populate.spec.js +++ b/tests/unit/utils/populate.spec.js @@ -1,9 +1,9 @@ -/* global describe expect it beforeEach */ import { getPopulateObj, getPopulates, getPopulateChild, getPopulateObjs, + getChildType, promisesForPopulate } from '../../../src/utils/populate' @@ -18,7 +18,22 @@ describe('Utils: Populate', () => { }) }) - describe('getPopulateObj', () => { + describe('getChildType', () => { + it('returns "string" for strings', () => { + expect(getChildType('some:value')).to.equal('string') + }) + it('returns "object" for objects', () => { + expect(getChildType({ some: 'val' })).to.equal('object') + }) + it('returns "array" for arrays', () => { + expect(getChildType([])).to.equal('array') + }) + it('returns "other" for other types', () => { + expect(getChildType(1)).to.equal('other') + }) + }) + + describe('getPopulateObjs', () => { it('returns object with child and root', () => { expect(getPopulateObjs(['some:value'])[0]).to.have.keys('child', 'root') }) @@ -47,14 +62,14 @@ describe('Utils: Populate', () => { describe('promisesForPopulate', () => { it('handles non-existant single child', () => - promisesForPopulate(Firebase, { uid: '123123' }, [{child: 'random', root: 'users'}]) + promisesForPopulate(Firebase, '', { uid: '123123' }, [{child: 'random', root: 'users'}]) .then((v) => { expect(JSON.stringify(v)).to.equal(JSON.stringify({})) }) ) it('populates single property containing a single item', () => - promisesForPopulate(Firebase, { uid: '123' }, [{child: 'uid', root: 'users'}]) + promisesForPopulate(Firebase, '', { uid: '123' }, [{child: 'uid', root: 'users'}]) .then((v) => { expect(v).to.exist expect(v).to.have.keys('users') @@ -63,7 +78,7 @@ describe('Utils: Populate', () => { ) it('populates single property containing a list', () => - promisesForPopulate(Firebase, { collaborators: { 'Iq5b0qK2NtgggT6U3bU6iZRGyma2': true, '123': true } }, [{child: 'collaborators', root: 'users'}]) + promisesForPopulate(Firebase, '', { collaborators: { 'Iq5b0qK2NtgggT6U3bU6iZRGyma2': true, '123': true } }, [{child: 'collaborators', root: 'users'}]) .then((v) => { expect(v).to.exist expect(v).to.have.keys('users') @@ -72,7 +87,7 @@ describe('Utils: Populate', () => { ) it('populates list with single property populate', () => - promisesForPopulate(Firebase, { 1: { owner: 'Iq5b0qK2NtgggT6U3bU6iZRGyma2' } }, [{child: 'owner', root: 'users'}]) + promisesForPopulate(Firebase, '', { 1: { owner: 'Iq5b0qK2NtgggT6U3bU6iZRGyma2' } }, [{child: 'owner', root: 'users'}]) .then((v) => { expect(v).to.have.keys('users') expect(v.users['Iq5b0qK2NtgggT6U3bU6iZRGyma2']).to.be.an.object @@ -80,7 +95,7 @@ describe('Utils: Populate', () => { ) it('populates list with property containing array property', () => - promisesForPopulate(Firebase, { 1: { collaborators: ['Iq5b0qK2NtgggT6U3bU6iZRGyma2', '123'] } }, [{child: 'collaborators', root: 'users'}]) + promisesForPopulate(Firebase, '', { 1: { collaborators: ['Iq5b0qK2NtgggT6U3bU6iZRGyma2', '123'] } }, [{child: 'collaborators', root: 'users'}]) .then((v) => { expect(v).to.exist expect(v).to.have.keys('users') @@ -89,7 +104,7 @@ describe('Utils: Populate', () => { ) it('populates list with property containing firebase list', () => - promisesForPopulate(Firebase, { 1: { collaborators: { 'Iq5b0qK2NtgggT6U3bU6iZRGyma2': true, '123': true } } }, [{child: 'collaborators', root: 'users'}]) + promisesForPopulate(Firebase, '', { 1: { collaborators: { 'Iq5b0qK2NtgggT6U3bU6iZRGyma2': true, '123': true } } }, [{child: 'collaborators', root: 'users'}]) .then((v) => { expect(v).to.exist expect(v).to.have.keys('users') @@ -98,7 +113,7 @@ describe('Utils: Populate', () => { ) it('populates list with property containing invalid child id', () => - promisesForPopulate(Firebase, { 1: { collaborators: ['1111', '123'] } }, [{child: 'collaborators', root: 'users'}]) + promisesForPopulate(Firebase, '', { 1: { collaborators: ['1111', '123'] } }, [{child: 'collaborators', root: 'users'}]) .then((v) => { expect(v).to.exist expect(v.users).to.have.keys('123') // sets valid child diff --git a/tests/unit/utils/query.spec.js b/tests/unit/utils/query.spec.js index caacfe848..4ea1c9714 100644 --- a/tests/unit/utils/query.spec.js +++ b/tests/unit/utils/query.spec.js @@ -1,4 +1,3 @@ -/* global describe expect it beforeEach */ import { getWatchPath, setWatcher, @@ -89,11 +88,30 @@ describe('Utils: Query', () => { 'value:/todos': 1, 'value:/todo': 2 } + spy = sinon.spy(console, 'warn') + + }) + afterEach(() => { + console.warn.restore() }) it('removes single watcher', () => { unsetWatcher(Firebase, dispatch, 'value', '/todos') expect(Firebase._.watchers['value:/todos']).to.be.undefined }) + it('handes dispatch on unset listener config', () => { + Firebase._.config.dispatchOnUnsetListener = true + unsetWatcher(Firebase, dispatch, 'value', '/todos') + expect(Firebase._.watchers['value:/todos']).to.be.undefined + }) + + it('warns for deprecated method name', () => { + Firebase._.config.distpatchOnUnsetListener = true + // TODO: confirm that console.warn is called with correct message + unsetWatcher(Firebase, dispatch, 'value', '/todos') + expect(Firebase._.watchers['value:/todos']).to.be.undefined + expect(spy).to.have.been.calledWith('config.distpatchOnUnsetListener is Depreceated and will be removed in future versions. Please use config.dispatchOnUnsetListener (dispatch spelled correctly).') + }) + it('decrements existings watcher count', () => { unsetWatcher(Firebase, dispatch, 'value', '/todo') expect(Firebase._.watchers['value:/todos']).to.equal(1) @@ -198,6 +216,17 @@ describe('Utils: Query', () => { .to .equal(equalTo) }) + it('does not parse if notParsed parameter passed', () => { + const child = 'emailAddress' + const equalTo = '123' + const queryParams = createQueryFromParams([`orderByChild=${child}`, 'notParsed', `equalTo=${equalTo}`]) + expect(queryParams.child) + .to + .equal(child) + expect(queryParams.equalTo) + .to + .equal(equalTo) + }) }) }) diff --git a/tests/unit/utils/storage.spec.js b/tests/unit/utils/storage.spec.js index 11c07f525..4d4fbf31c 100644 --- a/tests/unit/utils/storage.spec.js +++ b/tests/unit/utils/storage.spec.js @@ -1,4 +1,3 @@ -/* global describe expect it */ import { deleteFile } from '../../../src/utils/storage' const fakeFirebase = { _: { @@ -26,8 +25,11 @@ const fakeFirebase = { } describe('Utils: Storage', () => { describe('deleteFile', () => { - it('returns dbPath', () => { - expect(deleteFile(fakeFirebase, { path: 'some', dbPath: 'some' })).to.eventually.have.keys('dbPath') - }) + it('returns dbPath', () => + expect(deleteFile(fakeFirebase, { path: 'some', dbPath: 'some' })).to.eventually.have.keys(['path', 'dbPath']) + ) + it('returns dbPath', () => + expect(deleteFile(fakeFirebase, { path: 'some' })).to.eventually.have.keys('path') + ) }) }) diff --git a/webpack.config.js b/webpack.config.js index b768a844f..632339bcc 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl const config = { module: { - loaders: [ + rules: [ { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ } ] }, @@ -23,10 +23,7 @@ const config = { root: 'React' } }, - plugins: [ - new webpack.optimize.OccurrenceOrderPlugin(), - new webpack.optimize.DedupePlugin() - ] + plugins: [] } if (env === 'production') { @@ -55,8 +52,11 @@ config.plugins.push( 'process.env.NODE_ENV': JSON.stringify(env) }), new webpack.BannerPlugin( - `${pkg.name}${env === 'production' ? '.min' : ''}.js v${pkg.version}`, - { raw: false, entryOnly: true } + { + banner: `${pkg.name}${env === 'production' ? '.min' : ''}.js v${pkg.version}`, + raw: false, + entryOnly: true + } ) ) diff --git a/yarn.lock b/yarn.lock index 3674b9991..1d3588307 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,6 +24,12 @@ accepts@~1.3.3: mime-types "~2.1.11" negotiator "0.6.1" +acorn-dynamic-import@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" + dependencies: + acorn "^4.0.3" + acorn-globals@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" @@ -36,7 +42,7 @@ acorn-jsx@^3.0.0: dependencies: acorn "^3.0.4" -acorn@^3.0.0, acorn@^3.0.4: +acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" @@ -44,11 +50,11 @@ acorn@^4.0.11, acorn@^4.0.3, acorn@^4.0.4: version "4.0.11" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0" -acorn@^5.0.1: +acorn@^5.0.0, acorn@^5.0.1: version "5.0.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d" -ajv-keywords@^1.0.0: +ajv-keywords@^1.0.0, ajv-keywords@^1.1.1: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" @@ -99,6 +105,10 @@ ansistyles@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" +any-promise@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + anymatch@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" @@ -197,6 +207,14 @@ asap@^2.0.0, 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.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.1.tgz#48ba240b45a9280e94748990ba597d216617fd40" + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -229,24 +247,16 @@ async-some@~1.0.2: dependencies: dezalgo "^1.0.2" -async@1.x, async@^1.3.0, async@^1.4.0: +async@1.x, async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - -async@^2.0.1, async@^2.1.4: +async@^2.0.1, async@^2.1.2, async@^2.1.4: version "2.3.0" resolved "https://registry.yarnpkg.com/async/-/async-2.3.0.tgz#1013d1051047dd320fe24e494d5c66ecaf6147d9" dependencies: lodash "^4.14.0" -async@~0.2.6: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1081,6 +1091,10 @@ block-stream@*, block-stream@0.0.9: dependencies: inherits "~2.0.0" +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + body-parser@~1.14.0: version "1.14.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.14.2.tgz#1015cb1fe2c443858259581db53332f8d0cf50f9" @@ -1117,6 +1131,10 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + browser-resolve@^1.7.0: version "1.11.2" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" @@ -1127,12 +1145,51 @@ browser-stdout@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" -browserify-aes@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-0.4.0.tgz#067149b668df31c4b58533e02d01e806d8608e2c" +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a" + dependencies: + buffer-xor "^1.0.2" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + inherits "^2.0.1" + +browserify-cipher@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" inherits "^2.0.1" +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + browserify-zlib@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" @@ -1147,7 +1204,11 @@ buffer-shims@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" -buffer@^4.9.0: +buffer-xor@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + +buffer@^4.3.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" dependencies: @@ -1272,7 +1333,7 @@ chmodr@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chmodr/-/chmodr-1.0.2.tgz#04662b932d0f02ec66deaa2b0ea42811968e3eb9" -chokidar@^1.0.0, chokidar@^1.0.5, chokidar@^1.2.0, chokidar@^1.6.1: +chokidar@^1.0.5, chokidar@^1.2.0, chokidar@^1.4.3, chokidar@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" dependencies: @@ -1291,6 +1352,12 @@ chownr@^1.0.1, chownr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07" + dependencies: + inherits "^2.0.1" + circular-json@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" @@ -1301,10 +1368,23 @@ cli-cursor@^1.0.1, cli-cursor@^1.0.2: dependencies: restore-cursor "^1.0.1" +cli-table@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" + dependencies: + colors "1.0.3" + cli-width@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" +cli@0.11.3: + version "0.11.3" + resolved "https://registry.yarnpkg.com/cli/-/cli-0.11.3.tgz#7b0cd3de990e452925667c0dbaffdc9f7f2a9a15" + dependencies: + exit "0.1.2" + glob "^7.0.5" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -1360,6 +1440,14 @@ collapse-white-space@^1.0.0, collapse-white-space@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.2.tgz#9c463fb9c6d190d2dcae21a356a01bcae9eeef6d" +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + +colors@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + columnify@~1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" @@ -1464,6 +1552,33 @@ core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +create-ecdh@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + ripemd160 "^2.0.0" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.6" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + cross-env@^1.0.7: version "1.0.8" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-1.0.8.tgz#2bde748efc780f56ddf07ea69fcad875357774ce" @@ -1484,14 +1599,20 @@ cryptiles@2.x.x: dependencies: boom "2.x.x" -crypto-browserify@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.3.0.tgz#b9fc75bb4a0ed61dcf1cd5dae96eb30c9c3e506c" +crypto-browserify@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522" dependencies: - browserify-aes "0.4.0" - pbkdf2-compat "2.0.1" - ripemd160 "0.2.0" - sha.js "2.2.6" + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.2" @@ -1616,6 +1737,13 @@ depd@1.1.0, depd@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" +des.js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -1654,6 +1782,14 @@ diff@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" +diffie-hellman@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + disparity@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/disparity/-/disparity-2.0.0.tgz#57ddacb47324ae5f58d2cc0da886db4ce9eeb718" @@ -1752,7 +1888,7 @@ documentation@^4.0.0-beta15: vinyl-fs "^2.3.1" yargs "^4.3.1" -dom-storage@^2.0.2: +dom-storage@2.0.2, dom-storage@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.0.2.tgz#ed17cbf68abd10e0aef8182713e297c5e4b500b0" @@ -1808,6 +1944,18 @@ elegant-spinner@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" +elliptic@^6.0.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + emoji-regex@^6.0.0: version "6.4.1" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.4.1.tgz#77486fe9cd45421d260a6238b88d721e2fad2050" @@ -1832,13 +1980,14 @@ end-of-stream@1.0.0: dependencies: once "~1.3.0" -enhanced-resolve@~0.9.0: - version "0.9.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" +enhanced-resolve@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec" dependencies: graceful-fs "^4.1.2" - memory-fs "^0.2.0" - tapable "^0.1.8" + memory-fs "^0.4.0" + object-assign "^4.0.1" + tapable "^0.2.5" errno@^0.1.3: version "0.1.4" @@ -2076,6 +2225,10 @@ espree@^3.4.0: acorn "^5.0.1" acorn-jsx "^3.0.0" +esprima@^1.2.2: + version "1.2.5" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.2.5.tgz#0993502feaf668138325756f30f9a51feeec11e9" + esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -2128,10 +2281,20 @@ events@^1.0.0, events@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" +evp_bytestokey@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53" + dependencies: + create-hash "^1.1.1" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" +exit@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -2213,7 +2376,7 @@ faye-websocket@~0.10.0: dependencies: websocket-driver ">=0.5.1" -fbjs@^0.8.1, fbjs@^0.8.4: +fbjs@^0.8.1, fbjs@^0.8.4, fbjs@^0.8.9: version "0.8.12" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" dependencies: @@ -2291,6 +2454,29 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" +firebase-server@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/firebase-server/-/firebase-server-0.10.1.tgz#60f1c89c60adc5acb140c13a06229d4ebfc20700" + dependencies: + any-promise "1.3.0" + cli "0.11.3" + debug "2.2.0" + firebase "3.6.1" + jwt-simple "0.3.1" + lodash "3.10.1" + targaryen "2.3.3" + ws "1.1.1" + +firebase@3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-3.6.1.tgz#bcd7fe28f9eb75c8ecbefba9b4763b0800995229" + dependencies: + dom-storage "2.0.2" + faye-websocket "0.9.3" + jsonwebtoken "5.7.0" + rsvp "3.2.1" + xmlhttprequest "1.8.0" + firebase@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/firebase/-/firebase-3.9.0.tgz#c4237f50f58eeb25081b1839d6cbf175f8f7ed9b" @@ -2732,6 +2918,18 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" +hash-base@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" + dependencies: + inherits "^2.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573" + dependencies: + inherits "^2.0.1" + hawk@~3.1.0, hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -2745,6 +2943,14 @@ highlight.js@^9.1.0, highlight.js@^9.6.0: version "9.10.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.10.0.tgz#f9f0b14c0be00f0e4fb1e577b749fed9e6f52f55" +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" @@ -2876,10 +3082,6 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -interpret@^0.6.4: - version "0.6.6" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b" - interpret@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.2.tgz#f4f623f0bb7122f15f5717c8e254b8161b5c5b2d" @@ -3289,6 +3491,10 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" +json-loader@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" + json-parse-helpfulerror@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz#13f14ce02eed4e981297b64eb9e3b932e2dd13dc" @@ -3313,7 +3519,7 @@ json3@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" -json5@^0.5.0: +json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -3335,6 +3541,14 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-5.7.0.tgz#1c90f9a86ce5b748f5f979c12b70402b4afcddb4" + dependencies: + jws "^3.0.0" + ms "^0.7.1" + xtend "^4.0.1" + jsonwebtoken@^7.3.0: version "7.4.0" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.0.tgz#515bf2bba070ec615bad97fd2e945027eb476946" @@ -3369,7 +3583,7 @@ jwa@^1.1.4: ecdsa-sig-formatter "1.0.9" safe-buffer "^5.0.1" -jws@^3.1.4: +jws@^3.0.0, jws@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" dependencies: @@ -3381,6 +3595,10 @@ jwt-decode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" +jwt-simple@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/jwt-simple/-/jwt-simple-0.3.1.tgz#86e0b121d149534423dbd8044a727e3cf1eb939e" + kind-of@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" @@ -3437,7 +3655,11 @@ load-plugin@^2.0.0: npm-prefix "^1.2.0" resolve-from "^2.0.0" -loader-utils@^0.2.11, loader-utils@^0.2.16: +loader-runner@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" + +loader-utils@^0.2.16: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" dependencies: @@ -3587,6 +3809,10 @@ lodash.keys@~4.0.3: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.0.8.tgz#c0cf45d2fcf576c83055404d674c7e637c83ae81" +lodash.mergewith@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -3632,6 +3858,10 @@ lodash.without@~4.1.0: lodash._basedifference "~4.4.0" lodash.rest "^4.0.0" +lodash@3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + lodash@4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.5.1.tgz#80e8a074ca5f3893a6b1c10b2a636492d710c316" @@ -3669,7 +3899,7 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0, loose-envify@^1.1.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: @@ -3731,13 +3961,9 @@ media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" -memory-fs@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" - -memory-fs@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.3.0.tgz#7bcc6b629e3a43e871d7e29aca6ae8a7f15cbb20" +memory-fs@^0.4.0, memory-fs@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" dependencies: errno "^0.1.3" readable-stream "^2.0.1" @@ -3774,6 +4000,13 @@ micromatch@^2.1.5, micromatch@^2.1.6, micromatch@^2.3.7: parse-glob "^3.0.4" regex-cache "^0.4.2" +miller-rabin@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d" + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + mime-db@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" @@ -3788,6 +4021,14 @@ mime@1.3.4, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" +minimalistic-assert@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + minimatch@1: version "1.0.0" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-1.0.0.tgz#e0dd2120b49e1b724ce8d714c520822a9438576d" @@ -3932,16 +4173,16 @@ node-gyp@~3.6.0: tar "^2.0.0" which "1" -node-libs-browser@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.7.0.tgz#3e272c0819e308935e26674408d7af0e1491b83b" +node-libs-browser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" dependencies: assert "^1.1.1" browserify-zlib "^0.1.4" - buffer "^4.9.0" + buffer "^4.3.0" console-browserify "^1.1.0" constants-browserify "^1.0.0" - crypto-browserify "3.3.0" + crypto-browserify "^3.11.0" domain-browser "^1.1.1" events "^1.0.0" https-browserify "0.0.1" @@ -4322,7 +4563,7 @@ opener@^1.4.2, opener@~1.4.1: version "1.4.3" resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" -optimist@0.6.1, optimist@^0.6.1, optimist@~0.6.0: +optimist@0.6.1, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" dependencies: @@ -4340,6 +4581,10 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" +options@>=0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" + ordered-read-streams@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" @@ -4390,6 +4635,16 @@ parents@^1.0.0: dependencies: path-platform "~0.11.15" +parse-asn1@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + parse-entities@^1.0.0, parse-entities@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.0.tgz#4bc58f35fdc8e65dded35a12f2e40223ca24a3f7" @@ -4499,9 +4754,15 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -pbkdf2-compat@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288" +pbkdf2@^3.0.3: + version "3.0.12" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2" + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" performance-now@^0.2.0: version "0.2.0" @@ -4583,6 +4844,13 @@ promzard@^0.3.0: dependencies: read "1" +prop-types@^15.5.8: + version "15.5.10" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -4606,6 +4874,16 @@ pseudomap@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +public-encrypt@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -4657,6 +4935,10 @@ randomatic@^1.1.3: is-number "^2.0.2" kind-of "^3.0.2" +randombytes@^2.0.0, randombytes@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" + range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" @@ -5039,6 +5321,10 @@ replace-ext@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" +replaceall@^0.1.3: + version "0.1.6" + resolved "https://registry.yarnpkg.com/replaceall/-/replaceall-0.1.6.tgz#81d81ac7aeb72d7f5c4942adf2697a3220688d8e" + request@2, request@2.79.0, request@^2.47.0, request@^2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -5213,9 +5499,16 @@ rimraf@~2.5.2, rimraf@~2.5.4: dependencies: glob "^7.0.5" -ripemd160@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce" +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + dependencies: + hash-base "^2.0.0" + inherits "^2.0.1" + +rsvp@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a" run-async@^0.1.0: version "0.1.0" @@ -5227,7 +5520,7 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -safe-buffer@^5.0.1: +safe-buffer@^5.0.1, safe-buffer@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" @@ -5294,9 +5587,11 @@ setprototypeof@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" -sha.js@2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba" +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.8" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" + dependencies: + inherits "^2.0.1" sha@~2.0.1: version "2.0.1" @@ -5368,9 +5663,9 @@ sorted-object@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/sorted-object/-/sorted-object-2.0.1.tgz#7d631f4bd3a798a24af1dffcfbfe83337a5df5fc" -source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" +source-list-map@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1" source-map-support@^0.4.2: version "0.4.14" @@ -5378,13 +5673,13 @@ source-map-support@^0.4.2: dependencies: source-map "^0.5.6" -source-map@^0.4.4, source-map@~0.4.1: +source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" @@ -5469,7 +5764,7 @@ stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" -string-width@^1.0.0, string-width@^1.0.1: +string-width@^1.0.0, string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" dependencies: @@ -5525,7 +5820,7 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" -strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: +strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -5564,9 +5859,9 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" -tapable@^0.1.8, tapable@~0.1.8: - version "0.1.10" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" +tapable@^0.2.5, tapable@~0.2.5: + version "0.2.6" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" tar-pack@^3.4.0: version "3.4.0" @@ -5589,6 +5884,18 @@ tar@^2.0.0, tar@^2.2.1, tar@~2.2.1: fstream "^1.0.2" inherits "2" +targaryen@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/targaryen/-/targaryen-2.3.3.tgz#1014f2cc66f94c0f7a19233126b5f2168e25db2f" + dependencies: + cli-table "^0.3.1" + colors "^1.0.3" + esprima "^1.2.2" + lodash.mergewith "^4.6.0" + minimist "^1.1.0" + replaceall "^0.1.3" + strip-json-comments "^2.0.1" + text-encoding@0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" @@ -5760,14 +6067,14 @@ 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" -uglify-js@^2.6, uglify-js@~2.7.3: - version "2.7.5" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" +uglify-js@^2.6, uglify-js@^2.8.27: + version "2.8.27" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c" dependencies: - async "~0.2.6" source-map "~0.5.1" - uglify-to-browserify "~1.0.0" yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" uglify-to-browserify@~1.0.0: version "1.0.2" @@ -5777,6 +6084,14 @@ uid-number@0.0.6, uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" +ultron@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" + +ultron@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" + umask@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" @@ -6060,12 +6375,12 @@ ware@^1.3.0: dependencies: wrap-fn "^0.1.0" -watchpack@^0.2.1: - version "0.2.9" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-0.2.9.tgz#62eaa4ab5e5ba35fdfc018275626e3c0f5e3fb0b" +watchpack@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87" dependencies: - async "^0.9.0" - chokidar "^1.0.0" + async "^2.1.2" + chokidar "^1.4.3" graceful-fs "^4.1.2" wcwidth@^1.0.0: @@ -6097,32 +6412,38 @@ webpack-bundle-analyzer@^2.3.1: mkdirp "^0.5.1" opener "^1.4.2" -webpack-core@~0.6.9: - version "0.6.9" - resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" +webpack-sources@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb" dependencies: - source-list-map "~0.1.7" - source-map "~0.4.1" + source-list-map "^1.1.1" + source-map "~0.5.3" -webpack@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.14.0.tgz#54f1ffb92051a328a5b2057d6ae33c289462c823" +webpack@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.6.1.tgz#2e0457f0abb1ac5df3ab106c69c672f236785f07" dependencies: - acorn "^3.0.0" - async "^1.3.0" - clone "^1.0.2" - enhanced-resolve "~0.9.0" - interpret "^0.6.4" - loader-utils "^0.2.11" - memory-fs "~0.3.0" + acorn "^5.0.0" + acorn-dynamic-import "^2.0.0" + ajv "^4.7.0" + ajv-keywords "^1.1.1" + async "^2.1.2" + enhanced-resolve "^3.0.0" + interpret "^1.0.0" + json-loader "^0.5.4" + json5 "^0.5.1" + loader-runner "^2.3.0" + loader-utils "^0.2.16" + memory-fs "~0.4.1" mkdirp "~0.5.0" - node-libs-browser "^0.7.0" - optimist "~0.6.0" + node-libs-browser "^2.0.0" + source-map "^0.5.3" supports-color "^3.1.0" - tapable "~0.1.8" - uglify-js "~2.7.3" - watchpack "^0.2.1" - webpack-core "~0.6.9" + tapable "~0.2.5" + uglify-js "^2.8.27" + watchpack "^1.3.1" + webpack-sources "^0.2.3" + yargs "^6.0.0" websocket-driver@>=0.5.1: version "0.6.5" @@ -6218,6 +6539,20 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +ws@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018" + dependencies: + options ">=0.0.5" + ultron "1.0.x" + +ws@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.0.0.tgz#98ddb00056c8390cb751e7788788497f99103b6c" + dependencies: + safe-buffer "~5.0.1" + ultron "~1.1.0" + x-is-string@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" @@ -6226,7 +6561,7 @@ xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" -xmlhttprequest@^1.8.0: +xmlhttprequest@1.8.0, xmlhttprequest@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" @@ -6249,6 +6584,12 @@ yargs-parser@^2.4.1: camelcase "^3.0.0" lodash.assign "^4.0.6" +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + dependencies: + camelcase "^3.0.0" + yargs@^4.3.1: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" @@ -6268,6 +6609,24 @@ yargs@^4.3.1: y18n "^3.2.1" yargs-parser "^2.4.1" +yargs@^6.0.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"