diff --git a/.babelrc b/.babelrc index 0394a160d..a8fb8be61 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,6 @@ "react" ], "plugins": [ - "flow-react-proptypes", "transform-object-rest-spread", "transform-class-properties", "transform-flow-strip-types" diff --git a/.gitignore b/.gitignore index da7f48c53..a7d4ada28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Redux Offline specific ignores +lib/ + # Logs logs *.log diff --git a/README.md b/README.md index 18911642e..0ab065e41 100644 --- a/README.md +++ b/README.md @@ -36,27 +36,31 @@ Redux Offline is very, very new. If you find a bug, good job for being an early npm install --save redux-offline ``` -##### 2. Replace [redux createStore](http://redux.js.org/docs/api/createStore.html) with createOfflineStore +##### 2. Add the `offline` [store enhancer](http://redux.js.org/docs/Glossary.html#store-enhancer) with `compose` ```diff - import { applyMiddleware, createStore } from 'redux'; -+ import { applyMiddleware } from 'redux'; -+ import { createOfflineStore } from 'redux-offline'; ++ import { applyMiddleware, createStore, compose } from 'redux'; ++ import { offline } from 'redux-offline'; + import offlineConfig from 'redux-offline/lib/defaults'; // ... -- const store = createStore( -+ const store = createOfflineStore( +const store = createStore( reducer, preloadedState, - applyMiddleware(middleware), -+ offlineConfig +- applyMiddleware(middleware) ++ compose( ++ applyMiddleware(middleware), ++ offline(offlineConfig) ++ ) ); ``` See [Configuration](#configuration) for overriding default configurations. +Looking for `createOfflineStore` from redux-offline 1.x? See migration instructions in the [2.0.0 release notes](https://github.com/jevakallio/redux-offline/releases/tag/v2.0.0). + ##### 3. Decorate actions with offline metadata ```js @@ -208,7 +212,7 @@ const ordersReducer = (state, action) { The last part of the offline metadata is `meta.offline.effect`. This property can contain anything, and will be passed as-is to the effects reconciler. -The **effects reconciler** is a function that you pass to `createOfflineStore` configuration, whose responsibility it is to take the effect payload, send it over the network, and return a Promise that resolves if sending was successful or rejects if the sending failed. The method is passed the full action as a second parameter: +The **effects reconciler** is a function that you pass to offline enhancer configuration, whose responsibility it is to take the effect payload, send it over the network, and return a Promise that resolves if sending was successful or rejects if the sending failed. The method is passed the full action as a second parameter: ```js type EffectsReconciler = (effect: any, action: OfflineAction) => Promise @@ -300,25 +304,24 @@ export type Config = { }; ``` -#### Passing configuration to createOfflineStore -The `createOfflineStore` store creator takes the [configuration object](#configuration-object) as a final parameter: +#### Passing configuration to the enhancer +The `offline` store enhancer takes the [configuration object](#configuration-object) as a final parameter: ```diff -+ import { createOfflineStore } from 'redux-offline'; ++ import { offline } from 'redux-offline'; + import defaultConfig from 'redux-offline/lib/defaults'; -+ -- const store = createStore( -+ const store = createOfflineStore( + +const store = createStore( reducer, preloadedState, - middleware, -+ defaultConfig +- middleware ++ compose(middleware, offline(defaultConfig)) ); ``` #### Overriding default properties You can override any individual property in the default configuration: ```diff -import { createOfflineStore } from 'redux-offline'; +import { offline } from 'redux-offline'; import defaultConfig from 'redux-offline/lib/defaults'; const customConfig = { @@ -326,11 +329,11 @@ const customConfig = { effect: (effect, _action) => Api.send(effect) } -const store = createOfflineStore( +const store = createStore( reducer, preloadedState, - middleware, -+ customConfig +- middleware ++ compose(middleware, offline(customConfig)) ); ``` @@ -338,7 +341,7 @@ const store = createOfflineStore( The reason for default config is defined as a separate import is, that it pulls in the [redux-persist](https://github.com/rt2zz/redux-persist) dependency and a limited, but non-negligible amount of library code. If you want to minimize your bundle size, you'll want to avoid importing any code you don't use, and bring in only the pieces you need: ```diff -import { createOfflineStore } from 'redux-offline'; +import { offline } from 'redux-offline'; import batch from 'redux-offline/lib/defaults/batch'; import retry from 'redux-offline/lib/defaults/retry'; import discard from 'redux-offline/lib/defaults/discard'; @@ -351,11 +354,12 @@ const myConfig = { persist: (store) => MyCustomPersistence.persist(store) }; -const store = createOfflineStore( +const store = createStore( reducer, preloadedState, - middleware, -+ myConfig +- middleware ++ compose(middleware, offline(myConfig)) + myConfig ); ``` diff --git a/lib/actions.js b/lib/actions.js deleted file mode 100644 index 28d9fbb5a..000000000 --- a/lib/actions.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.busy = exports.completeRetry = exports.scheduleRetry = exports.networkStatusChanged = undefined; - -var _constants = require('./constants'); - -var networkStatusChanged = exports.networkStatusChanged = function networkStatusChanged(online) { - return { - type: _constants.OFFLINE_STATUS_CHANGED, - payload: { - online: online - } - }; -}; - -var scheduleRetry = exports.scheduleRetry = function scheduleRetry() { - var delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; - return { - type: _constants.OFFLINE_SCHEDULE_RETRY, - payload: { - delay: delay - } - }; -}; - -var completeRetry = exports.completeRetry = function completeRetry(action, retryToken) { - return { - type: _constants.OFFLINE_COMPLETE_RETRY, - payload: action, - meta: { retryToken: retryToken } - }; -}; - -var busy = exports.busy = function busy(isBusy) { - return { - type: _constants.OFFLINE_BUSY, - payload: { busy: isBusy } - }; -}; \ No newline at end of file diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index 06e1eee9e..000000000 --- a/lib/config.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.applyDefaults = undefined; - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var _defaults = require('./defaults'); - -var _defaults2 = _interopRequireDefault(_defaults); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var babelPluginFlowReactPropTypes_proptype_Config = require('./types').babelPluginFlowReactPropTypes_proptype_Config || require('react').PropTypes.any; -/*global $Shape*/ - - -var applyDefaults = exports.applyDefaults = function applyDefaults() { - var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - return _extends({}, _defaults2.default, config); -}; \ No newline at end of file diff --git a/lib/constants.js b/lib/constants.js deleted file mode 100644 index b75b59410..000000000 --- a/lib/constants.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var OFFLINE_STATUS_CHANGED = exports.OFFLINE_STATUS_CHANGED = 'Offline/STATUS_CHANGED'; -var OFFLINE_SCHEDULE_RETRY = exports.OFFLINE_SCHEDULE_RETRY = 'Offline/SCHEDULE_RETRY'; -var OFFLINE_COMPLETE_RETRY = exports.OFFLINE_COMPLETE_RETRY = 'Offline/COMPLETE_RETRY'; -var OFFLINE_SEND = exports.OFFLINE_SEND = 'Offline/SEND'; -var OFFLINE_BUSY = exports.OFFLINE_BUSY = 'Offline/BUSY'; \ No newline at end of file diff --git a/lib/defaults/batch.js b/lib/defaults/batch.js deleted file mode 100644 index e36f3d73d..000000000 --- a/lib/defaults/batch.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var babelPluginFlowReactPropTypes_proptype_Outbox = require('../types').babelPluginFlowReactPropTypes_proptype_Outbox || require('react').PropTypes.any; - -exports.default = function (outbox) { - if (outbox.length > 0) { - return [outbox[0]]; - } - return []; -}; \ No newline at end of file diff --git a/lib/defaults/detectNetwork.js b/lib/defaults/detectNetwork.js deleted file mode 100644 index 077dfb50b..000000000 --- a/lib/defaults/detectNetwork.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -/*global window*/ - -var handle = function handle(callback, result) { - if (window.requestAnimationFrame) { - window.requestAnimationFrame(function () { - return callback(result); - }); - } else { - setTimeout(function () { - return callback(result); - }, 0); - } -}; - -exports.default = function (callback) { - if (typeof window !== 'undefined' && window.addEventListener) { - window.addEventListener('online', function () { - return handle(callback, true); - }); - window.addEventListener('offline', function () { - return handle(callback, false); - }); - handle(callback, window.navigator.onLine); - } -}; diff --git a/lib/defaults/detectNetwork.native.js b/lib/defaults/detectNetwork.native.js deleted file mode 100644 index b2e7959b8..000000000 --- a/lib/defaults/detectNetwork.native.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _reactNative = require('react-native'); - -//eslint-disable-line - -exports.default = function (callback) { - var wasOnline = void 0; - var updateState = function updateState(isOnline) { - if (wasOnline !== isOnline) { - wasOnline = isOnline; - callback(isOnline); - } - }; - - _reactNative.NetInfo.isConnected.addEventListener('change', updateState); - _reactNative.NetInfo.isConnected.fetch().then(updateState); - _reactNative.AppState.addEventListener('change', function () { - _reactNative.NetInfo.isConnected.fetch().then(updateState); - }); -}; \ No newline at end of file diff --git a/lib/defaults/discard.js b/lib/defaults/discard.js deleted file mode 100644 index 6fb5496d5..000000000 --- a/lib/defaults/discard.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _effect = require('./effect'); - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -exports.default = function (error, action) { - var _retries = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; - - // not a network error -> discard - if (!('status' in error)) { - return true; - } - - //discard http 4xx errors - //$FlowFixMe - return error.status >= 400 && error.status < 500; -}; \ No newline at end of file diff --git a/lib/defaults/effect.js b/lib/defaults/effect.js deleted file mode 100644 index 8160af463..000000000 --- a/lib/defaults/effect.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -exports.NetworkError = NetworkError; - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; -/*global fetch*/ - -function NetworkError(response, status) { - this.name = 'NetworkError'; - this.status = status; - this.response = response; -} - -//$FlowFixMe -NetworkError.prototype = Error.prototype; -NetworkError.prototype.status = null; - -var getResponseBody = function getResponseBody(res) { - var contentType = res.headers.get('content-type'); - return contentType.indexOf('json') >= 0 ? res.json() : res.text(); -}; - -exports.default = function (effect, _action) { - var url = effect.url, - options = _objectWithoutProperties(effect, ['url']); - - var headers = _extends({ 'content-type': 'application/json' }, options.headers); - return fetch(url, _extends({}, options, { headers: headers })).then(function (res) { - if (res.ok) { - return getResponseBody(res); - } else { - return getResponseBody(res).then(function (body) { - throw new NetworkError(body, res.status); - }); - } - }); -}; \ No newline at end of file diff --git a/lib/defaults/index.js b/lib/defaults/index.js deleted file mode 100644 index b02eb46d9..000000000 --- a/lib/defaults/index.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _persist = require('./persist'); - -var _persist2 = _interopRequireDefault(_persist); - -var _detectNetwork = require('./detectNetwork'); - -var _detectNetwork2 = _interopRequireDefault(_detectNetwork); - -var _effect = require('./effect'); - -var _effect2 = _interopRequireDefault(_effect); - -var _batch = require('./batch'); - -var _batch2 = _interopRequireDefault(_batch); - -var _retry = require('./retry'); - -var _retry2 = _interopRequireDefault(_retry); - -var _discard = require('./discard'); - -var _discard2 = _interopRequireDefault(_discard); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = { - rehydrate: true, - persist: _persist2.default, - detectNetwork: _detectNetwork2.default, - batch: _batch2.default, - effect: _effect2.default, - retry: _retry2.default, - discard: _discard2.default -}; \ No newline at end of file diff --git a/lib/defaults/persist.js b/lib/defaults/persist.js deleted file mode 100644 index 34dbbfc91..000000000 --- a/lib/defaults/persist.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _reduxPersist = require('redux-persist'); - -exports.default = _reduxPersist.persistStore; // flow \ No newline at end of file diff --git a/lib/defaults/persist.native.js b/lib/defaults/persist.native.js deleted file mode 100644 index 65cbd0112..000000000 --- a/lib/defaults/persist.native.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -//$FlowIgnore -//eslint-disable-line import/no-unresolved - - -var _reactNative = require('react-native'); - -var _reduxPersist = require('redux-persist'); - -exports.default = function (store, options, callback) { - return (0, _reduxPersist.persistStore)(store, _extends({ storage: _reactNative.AsyncStorage }, options), callback); -}; \ No newline at end of file diff --git a/lib/defaults/retry.js b/lib/defaults/retry.js deleted file mode 100644 index 764711714..000000000 --- a/lib/defaults/retry.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var decaySchedule = [1000, //After 1 seconds -1000 * 5, //After 5 seconds -1000 * 15, //After 15 seconds -1000 * 30, //After 30 seconds -1000 * 60, //After 1 minute -1000 * 60 * 3, //After 3 minutes -1000 * 60 * 5, //After 5 minutes -1000 * 60 * 10, //After 10 minutes -1000 * 60 * 30, //After 30 minutes -1000 * 60 * 60 //After 1 hour -]; - -exports.default = function (action, retries) { - return decaySchedule[retries] || null; -}; \ No newline at end of file diff --git a/lib/defaults/send.js b/lib/defaults/send.js deleted file mode 100644 index 33695b2ca..000000000 --- a/lib/defaults/send.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; -/*global fetch*/ - -exports.default = function (effect, _action) { - var url = effect.url, - options = _objectWithoutProperties(effect, ['url']); - - return fetch(url, options); -}; \ No newline at end of file diff --git a/lib/effects.js b/lib/effects.js deleted file mode 100644 index e94859194..000000000 --- a/lib/effects.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -var babelPluginFlowReactPropTypes_proptype_Outbox = require('./types').babelPluginFlowReactPropTypes_proptype_Outbox || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_ResultAction = require('./types').babelPluginFlowReactPropTypes_proptype_ResultAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('./types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_Config = require('./types').babelPluginFlowReactPropTypes_proptype_Config || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_AppState = require('./types').babelPluginFlowReactPropTypes_proptype_AppState || require('react').PropTypes.any; - -var scheduleRetry = function scheduleRetry() { - var delay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; - - return { type: 'Offline/SCHEDULE_RETRY', payload: { delay: delay } }; -}; - -// const completeRetry = action => { -// return { type: 'Offline/COMPLETE_RETRY', payload: action }; -// }; -// -// const delay = (timeout = 0) => { -// return new Promise(resolve => setTimeout(resolve, timeout)); -// }; - -var complete = function complete(action, success, payload) { - return _extends({}, action, { payload: payload, meta: _extends({}, action.meta, { success: success, completed: true }) }); -}; - -var take = function take(state, config) { - return config.strategy.batching(state.offline.outbox); -}; - -var send = function send(action, dispatch, config) { - var retries = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; - - var metadata = action.meta.offline; - return config.strategy.network(metadata.effect, action).then(function (result) { - return dispatch(complete(metadata.commit, true, result)); - }).catch(function (error) { - var retry = config.strategy.retry(action, retries); - if (retry) { - return dispatch(scheduleRetry(retry.delay)); - } else { - return dispatch(complete(metadata.rollback, false, error)); - } - }); -}; - -var addEffects = exports.addEffects = function addEffects(store, config) { - store.subscribe(function () { - // find any actions to send, if any - var state = store.getState(); - var actions = take(state, config); - - // if the are any actions in the queue that we are not - // yet processing, send those actions - if (actions.length > 0 && !state.offline.busy && state.offline.online) { - // @TODO: batching - send(actions[0], store.dispatch, config); - } - - // @TODO: retry - // if (action.type === 'Offline/SCHEDULE_RETRY') { - // //retryToken = retryToken++; - // delay(action.payload.delay).then(() => store.dispatch(completeRetry(action))); - // } - }); -}; \ No newline at end of file diff --git a/lib/impl.js b/lib/impl.js deleted file mode 100644 index 9a390c31f..000000000 --- a/lib/impl.js +++ /dev/null @@ -1 +0,0 @@ -"use strict"; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 1f5714e9c..0c44f8f01 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); -exports.createOfflineStore = undefined; +exports.offline = undefined; var _redux = require('redux'); @@ -19,52 +19,57 @@ var _config = require('./config'); var _actions = require('./actions'); -var babelPluginFlowReactPropTypes_proptype_Config = require('./types').babelPluginFlowReactPropTypes_proptype_Config || require('react').PropTypes.any; -/*global $Shape*/ - - // @TODO: Take createStore as config? // eslint-disable-next-line no-unused-vars + +/*global $Shape*/ var persistor = void 0; var autoRehydrate = _reduxPersist.autoRehydrate; -var createOfflineStore = exports.createOfflineStore = function createOfflineStore(reducer, preloadedState, enhancer) { - var userConfig = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; +var offline = exports.offline = function offline() { + var userConfig = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + return function (createStore) { + return function (reducer, preloadedState) { + var enhancer = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (x) { + return x; + }; - console.log('user config', userConfig); - var config = (0, _config.applyDefaults)(userConfig); + console.log('user config', userConfig); + var config = (0, _config.applyDefaults)(userConfig); - console.log('Creating offline store', config); + console.log('Creating offline store', config); - // wraps userland reducer with a top-level - // reducer that handles offline state updating - var offlineReducer = (0, _updater.enhanceReducer)(reducer, config); + // wraps userland reducer with a top-level + // reducer that handles offline state updating + var offlineReducer = (0, _updater.enhanceReducer)(reducer, config); - var offlineMiddleware = (0, _redux.applyMiddleware)((0, _middleware.createOfflineMiddleware)(config)); + var offlineMiddleware = (0, _redux.applyMiddleware)((0, _middleware.createOfflineMiddleware)(config)); - if (config.immutable) { - autoRehydrate = _reduxPersistImmutable.autoRehydrate; - config.persist = _reduxPersistImmutable.persistStore; - } + if (config.immutable) { + autoRehydrate = _reduxPersistImmutable.autoRehydrate; + config.persist = _reduxPersistImmutable.persistStore; + } - // create autoRehydrate enhancer if required - var offlineEnhancer = config.persist && config.rehydrate ? (0, _redux.compose)(offlineMiddleware, enhancer, autoRehydrate()) : (0, _redux.compose)(offlineMiddleware, enhancer); + // create autoRehydrate enhancer if required + var offlineEnhancer = config.persist && config.rehydrate ? (0, _redux.compose)(offlineMiddleware, enhancer, autoRehydrate()) : (0, _redux.compose)(offlineMiddleware, enhancer); - // create store - var store = (0, _redux.createStore)(offlineReducer, preloadedState, offlineEnhancer); + // create store + var store = createStore(offlineReducer, preloadedState, offlineEnhancer); - // launch store persistor - if (config.persist) { - persistor = config.persist(store, config.persistOptions, config.persistCallback); - } + // launch store persistor + if (config.persist) { + persistor = config.persist(store, config.persistOptions, config.persistCallback); + } - // launch network detector - if (config.detectNetwork) { - config.detectNetwork(function (online) { - store.dispatch((0, _actions.networkStatusChanged)(online)); - }); - } + // launch network detector + if (config.detectNetwork) { + config.detectNetwork(function (online) { + store.dispatch((0, _actions.networkStatusChanged)(online)); + }); + } - return store; + return store; + }; + }; }; \ No newline at end of file diff --git a/lib/middleware.js b/lib/middleware.js index 9b56e2129..a2410bd99 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -11,16 +11,6 @@ var _constants = require('./constants'); var _actions = require('./actions'); -var babelPluginFlowReactPropTypes_proptype_Outbox = require('./types').babelPluginFlowReactPropTypes_proptype_Outbox || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_ResultAction = require('./types').babelPluginFlowReactPropTypes_proptype_ResultAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('./types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_Config = require('./types').babelPluginFlowReactPropTypes_proptype_Config || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_AppState = require('./types').babelPluginFlowReactPropTypes_proptype_AppState || require('react').PropTypes.any; - var after = function after() { var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; @@ -63,8 +53,6 @@ var send = function send(action, dispatch, config) { console.log('Discarding action', action.type, 'because retry did not return a delay'); return dispatch(complete(metadata.rollback, false, error)); } - }).finally(function () { - return dispatch((0, _actions.busy)(false)); }); }; diff --git a/lib/persist.js b/lib/persist.js deleted file mode 100644 index 329ed1112..000000000 --- a/lib/persist.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.persist = undefined; - -var _reduxPersist = require('redux-persist'); - -var persist = exports.persist = function persist(store) { - return (0, _reduxPersist.persistStore)(store); -}; \ No newline at end of file diff --git a/lib/persist.native.js b/lib/persist.native.js deleted file mode 100644 index befd1ce78..000000000 --- a/lib/persist.native.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.persist = undefined; - -var _reactNative = require('react-native'); - -var _reduxPersist = require('redux-persist'); - -var persist = exports.persist = function persist(store) { - return (0, _reduxPersist.persistStore)(store, { storage: _reactNative.AsyncStorage }); -}; \ No newline at end of file diff --git a/lib/reducer.js b/lib/reducer.js deleted file mode 100644 index e35ea40ff..000000000 --- a/lib/reducer.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineState = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineState || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_State = require('../types').babelPluginFlowReactPropTypes_proptype_State || require('react').PropTypes.any; - -function offlineReducer(state, action) {} \ No newline at end of file diff --git a/lib/reducers.js b/lib/reducers.js deleted file mode 100644 index e35ea40ff..000000000 --- a/lib/reducers.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineState = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineState || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_State = require('../types').babelPluginFlowReactPropTypes_proptype_State || require('react').PropTypes.any; - -function offlineReducer(state, action) {} \ No newline at end of file diff --git a/lib/reducers/index.js b/lib/reducers/index.js deleted file mode 100644 index e35ea40ff..000000000 --- a/lib/reducers/index.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineState = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineState || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_State = require('../types').babelPluginFlowReactPropTypes_proptype_State || require('react').PropTypes.any; - -function offlineReducer(state, action) {} \ No newline at end of file diff --git a/lib/reducers/reducers.js b/lib/reducers/reducers.js deleted file mode 100644 index e35ea40ff..000000000 --- a/lib/reducers/reducers.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineState = require('../types').babelPluginFlowReactPropTypes_proptype_OfflineState || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_State = require('../types').babelPluginFlowReactPropTypes_proptype_State || require('react').PropTypes.any; - -function offlineReducer(state, action) {} \ No newline at end of file diff --git a/lib/storage.js b/lib/storage.js deleted file mode 100644 index b4d732137..000000000 --- a/lib/storage.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = window.localStorage; \ No newline at end of file diff --git a/lib/storage.native.js b/lib/storage.native.js deleted file mode 100644 index 92bceebc5..000000000 --- a/lib/storage.native.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _reactNative = require('react-native'); - -exports.default = _reactNative.AsyncStorage; \ No newline at end of file diff --git a/lib/strategy.js b/lib/strategy.js deleted file mode 100644 index 9a390c31f..000000000 --- a/lib/strategy.js +++ /dev/null @@ -1 +0,0 @@ -"use strict"; \ No newline at end of file diff --git a/lib/types.js b/lib/types.js index 0e2b814b0..9a390c31f 100644 --- a/lib/types.js +++ b/lib/types.js @@ -1,168 +1 @@ -"use strict"; - -Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_ResultAction", { - value: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }) -}); -Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_OfflineMetadata", { - value: require("react").PropTypes.shape({ - effect: require("react").PropTypes.shape({}).isRequired, - commit: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired, - rollback: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired - }) -}); -Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_Receipt", { - value: require("react").PropTypes.shape({ - message: require("react").PropTypes.shape({ - effect: require("react").PropTypes.shape({}).isRequired, - commit: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired, - rollback: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired - }).isRequired, - success: require("react").PropTypes.bool.isRequired, - result: require("react").PropTypes.shape({}).isRequired - }) -}); -Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_OfflineAction", { - value: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - transaction: require("react").PropTypes.number, - offline: require("react").PropTypes.shape({ - effect: require("react").PropTypes.shape({}).isRequired, - commit: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired, - rollback: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired - }).isRequired - }).isRequired - }) -}); -Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_OfflineState", { - value: require("react").PropTypes.shape({ - lastTransaction: require("react").PropTypes.number.isRequired, - online: require("react").PropTypes.bool.isRequired, - outbox: require("react").PropTypes.any.isRequired, - receipts: require("react").PropTypes.arrayOf(require("react").PropTypes.shape({ - message: require("react").PropTypes.shape({ - effect: require("react").PropTypes.shape({}).isRequired, - commit: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired, - rollback: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired - }).isRequired, - success: require("react").PropTypes.bool.isRequired, - result: require("react").PropTypes.shape({}).isRequired - })).isRequired, - retryCount: require("react").PropTypes.number.isRequired, - retryToken: require("react").PropTypes.number.isRequired, - retryScheduled: require("react").PropTypes.bool.isRequired - }) -}); -Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_AppState", { - value: require("react").PropTypes.shape({ - offline: require("react").PropTypes.shape({ - lastTransaction: require("react").PropTypes.number.isRequired, - online: require("react").PropTypes.bool.isRequired, - outbox: require("react").PropTypes.any.isRequired, - receipts: require("react").PropTypes.arrayOf(require("react").PropTypes.shape({ - message: require("react").PropTypes.shape({ - effect: require("react").PropTypes.shape({}).isRequired, - commit: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired, - rollback: require("react").PropTypes.shape({ - type: require("react").PropTypes.string.isRequired, - payload: require("react").PropTypes.shape({}), - meta: require("react").PropTypes.shape({ - success: require("react").PropTypes.bool.isRequired, - completed: require("react").PropTypes.bool.isRequired - }).isRequired - }).isRequired - }).isRequired, - success: require("react").PropTypes.bool.isRequired, - result: require("react").PropTypes.shape({}).isRequired - })).isRequired, - retryCount: require("react").PropTypes.number.isRequired, - retryToken: require("react").PropTypes.number.isRequired, - retryScheduled: require("react").PropTypes.bool.isRequired - }).isRequired, - toJS: require("react").PropTypes.func - }) -}); -Object.defineProperty(module.exports, "babelPluginFlowReactPropTypes_proptype_Config", { - value: require("react").PropTypes.shape({ - batch: require("react").PropTypes.func.isRequired, - detectNetwork: require("react").PropTypes.func.isRequired, - persist: require("react").PropTypes.func.isRequired, - effect: require("react").PropTypes.func.isRequired, - retry: require("react").PropTypes.func.isRequired, - discard: require("react").PropTypes.func.isRequired, - persistOptions: require("react").PropTypes.shape({}).isRequired, - immutable: require("react").PropTypes.bool.isRequired, - persistCallback: require("react").PropTypes.func.isRequired - }) -}); \ No newline at end of file +"use strict"; \ No newline at end of file diff --git a/lib/updater.js b/lib/updater.js index f6f3b74e1..a21ba667e 100644 --- a/lib/updater.js +++ b/lib/updater.js @@ -15,15 +15,8 @@ function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } -var babelPluginFlowReactPropTypes_proptype_Config = require('./types').babelPluginFlowReactPropTypes_proptype_Config || require('react').PropTypes.any; /* global */ -var babelPluginFlowReactPropTypes_proptype_ResultAction = require('./types').babelPluginFlowReactPropTypes_proptype_ResultAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineAction = require('./types').babelPluginFlowReactPropTypes_proptype_OfflineAction || require('react').PropTypes.any; - -var babelPluginFlowReactPropTypes_proptype_OfflineState = require('./types').babelPluginFlowReactPropTypes_proptype_OfflineState || require('react').PropTypes.any; - var enqueue = function enqueue(state, action) { var transaction = state.lastTransaction + 1; var stamped = _extends({}, action, { meta: _extends({}, action.meta, { transaction: transaction }) }); @@ -38,7 +31,7 @@ var dequeue = function dequeue(state) { var _state$outbox = _toArray(state.outbox), rest = _state$outbox.slice(1); - return _extends({}, state, { outbox: rest, retryCount: 0 }); + return _extends({}, state, { outbox: rest, retryCount: 0, busy: false }); }; var initialState = { @@ -63,8 +56,13 @@ var offlineUpdater = function offlineUpdater() { return _extends({}, state, { online: action.payload.online }); } + if (action.type === _constants.PERSIST_REHYDRATE) { + return _extends({}, state, { busy: false }); + } + if (action.type === _constants.OFFLINE_SCHEDULE_RETRY) { return _extends({}, state, { + busy: false, retryScheduled: true, retryCount: state.retryCount + 1, retryToken: state.retryToken + 1 @@ -75,7 +73,7 @@ var offlineUpdater = function offlineUpdater() { return _extends({}, state, { retryScheduled: false }); } - if (action.type === _constants.OFFLINE_BUSY) { + if (action.type === _constants.OFFLINE_BUSY && action.payload && typeof action.payload.busy === 'boolean') { return _extends({}, state, { busy: action.payload.busy }); } diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index be488e872..000000000 --- a/lib/utils.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } - -var warn = exports.warn = function warn() { - var _console; - - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - return (_console = console).warn.apply(_console, ['[redux-offline]'].concat(_toConsumableArray(args))); -}; \ No newline at end of file diff --git a/package.json b/package.json index 7c93bbd07..8f37e3af0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redux-offline", - "version": "1.0.3", + "version": "1.1.0", "description": "Redux Offline-First Architecture", "main": "lib/index.js", "scripts": { @@ -9,6 +9,7 @@ "flow:stop": "flow stop", "flow": "flow; test $? -eq 0 -o $? -eq 2", "lint": "eslint src/", + "prepublish": "npm run lint && npm run flow && npm run test && npm run build", "test": "jest", "test:watch": "jest --watch", "watch": "npm run build -- --watch" @@ -31,7 +32,6 @@ "babel-core": "^6.18.2", "babel-eslint": "^7.1.1", "babel-loader": "^6.2.8", - "babel-plugin-flow-react-proptypes": "^0.18.1", "babel-plugin-transform-class-properties": "^6.19.0", "babel-plugin-transform-flow-strip-types": "^6.18.0", "babel-plugin-transform-object-rest-spread": "^6.19.0", diff --git a/src/constants.js b/src/constants.js index 055334151..ae2d4b343 100644 --- a/src/constants.js +++ b/src/constants.js @@ -3,3 +3,4 @@ export const OFFLINE_SCHEDULE_RETRY = 'Offline/SCHEDULE_RETRY'; export const OFFLINE_COMPLETE_RETRY = 'Offline/COMPLETE_RETRY'; export const OFFLINE_SEND = 'Offline/SEND'; export const OFFLINE_BUSY = 'Offline/BUSY'; +export const PERSIST_REHYDRATE = 'persist/REHYDRATE'; diff --git a/src/defaults/effect.js b/src/defaults/effect.js index 18d6b919c..130ec4148 100644 --- a/src/defaults/effect.js +++ b/src/defaults/effect.js @@ -13,9 +13,20 @@ export function NetworkError(response: {} | string, status: number) { NetworkError.prototype = Error.prototype; NetworkError.prototype.status = null; -const getResponseBody = res => { +const tryParseJSON = (json: string): ?{} => { + if (!json) { + return null; + } + try { + return JSON.parse(json); + } catch (e) { + throw new Error(`Failed to parse unexpected JSON response: ${json}`); + } +}; + +const getResponseBody = (res: any): Promise<{} | string> => { const contentType = res.headers.get('content-type'); - return contentType.indexOf('json') >= 0 ? res.json() : res.text(); + return contentType.indexOf('json') >= 0 ? res.text().then(tryParseJSON) : res.text(); }; export default (effect: any, _action: OfflineAction): Promise => { @@ -26,7 +37,7 @@ export default (effect: any, _action: OfflineAction): Promise => { return getResponseBody(res); } else { return getResponseBody(res).then(body => { - throw new NetworkError(body, res.status); + throw new NetworkError(body || '', res.status); }); } }); diff --git a/src/index.js b/src/index.js index 1ee73de0f..aa62e3d62 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ // @flow /*global $Shape*/ import type { Config } from './types'; -import { applyMiddleware, createStore, compose } from 'redux'; +import { applyMiddleware, compose } from 'redux'; import { autoRehydrate as reduxAutoRehydrate } from 'redux-persist'; import { persistStore as immutablePersistStore, @@ -18,11 +18,10 @@ import { networkStatusChanged } from './actions'; let persistor; let autoRehydrate = reduxAutoRehydrate; -export const createOfflineStore = ( +export const offline = (userConfig: $Shape = {}) => (createStore: any) => ( reducer: any, preloadedState: any, - enhancer: any, - userConfig: $Shape = {} + enhancer: any = x => x ) => { console.log('user config', userConfig); const config = applyDefaults(userConfig); diff --git a/src/middleware.js b/src/middleware.js index edc2ab837..6ba74c55e 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -41,8 +41,7 @@ const send = (action: OfflineAction, dispatch, config: Config, retries = 0) => { console.log('Discarding action', action.type, 'because retry did not return a delay'); return dispatch(complete(metadata.rollback, false, error)); } - }) - .finally(() => dispatch(busy(false))); + }); }; export const createOfflineMiddleware = (config: Config) => (store: any) => (next: any) => ( diff --git a/src/updater.js b/src/updater.js index 3f35b5723..7dc94440b 100644 --- a/src/updater.js +++ b/src/updater.js @@ -6,7 +6,8 @@ import { OFFLINE_STATUS_CHANGED, OFFLINE_SCHEDULE_RETRY, OFFLINE_COMPLETE_RETRY, - OFFLINE_BUSY + OFFLINE_BUSY, + PERSIST_REHYDRATE } from './constants'; type ControlAction = @@ -26,7 +27,7 @@ const enqueue = (state: OfflineState, action: any): OfflineState => { const dequeue = (state: OfflineState): OfflineState => { const [, ...rest] = state.outbox; - return { ...state, outbox: rest, retryCount: 0 }; + return { ...state, outbox: rest, retryCount: 0, busy: false }; }; const initialState: OfflineState = { @@ -55,9 +56,14 @@ const offlineUpdater = function offlineUpdater( return { ...state, online: action.payload.online }; } + if (action.type === PERSIST_REHYDRATE) { + return { ...state, busy: false }; + } + if (action.type === OFFLINE_SCHEDULE_RETRY) { return { ...state, + busy: false, retryScheduled: true, retryCount: state.retryCount + 1, retryToken: state.retryToken + 1 @@ -68,7 +74,7 @@ const offlineUpdater = function offlineUpdater( return { ...state, retryScheduled: false }; } - if (action.type === OFFLINE_BUSY) { + if (action.type === OFFLINE_BUSY && action.payload && typeof action.payload.busy === 'boolean') { return { ...state, busy: action.payload.busy }; } @@ -85,24 +91,25 @@ const offlineUpdater = function offlineUpdater( return state; }; -export const enhanceReducer = (reducer: any, config: Config) => (state: any, action: any) => { - let offlineState; - let restState; - if (typeof state !== 'undefined') { - const { offline, ...rest } = state; - if (config.immutable) { - offlineState = state.get('offline'); - restState = state.delete('offline'); - - return reducer(restState, action) - .set('offline', offlineUpdater(offlineState, action)); +export const enhanceReducer = (reducer: any, config: Config) => + (state: any, action: any) => { + let offlineState; + let restState; + if (typeof state !== 'undefined') { + const { offline, ...rest } = state; + if (config.immutable) { + offlineState = state.get('offline'); + restState = state.delete('offline'); + + return reducer(restState, action) + .set('offline', offlineUpdater(offlineState, action)); + } + offlineState = offline; + restState = rest; } - offlineState = offline; - restState = rest; - } - return { - ...reducer(restState, action), - offline: offlineUpdater(offlineState, action) + return { + ...reducer(restState, action), + offline: offlineUpdater(offlineState, action) + }; }; -};