From 02f1e118aedb2e0119f2360e728df3244e887cc7 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Thu, 22 Feb 2018 21:59:46 -0800 Subject: [PATCH] feat(notifications): implement connection to new API --- package.json | 2 +- src/actions/notification.js | 14 ++++++ src/containers/home/index.js | 50 ++++++++++++++-------- src/reducers/index.js | 2 + src/reducers/notification.js | 27 ++++++++++++ src/sagas/index.js | 8 +++- src/sagas/notification.js | 83 ++++++++++++++++++++++++++++++++++++ src/sagas/wallet.js | 2 +- yarn.lock | 6 +-- 9 files changed, 171 insertions(+), 23 deletions(-) create mode 100644 src/actions/notification.js create mode 100644 src/reducers/notification.js create mode 100644 src/sagas/notification.js diff --git a/package.json b/package.json index c49f9a4..df7d608 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "ethjs": "^0.3.3", "ethjs-unit": "^0.1.6", "history": "^4.7.2", - "kleros-api": "^0.0.58", + "kleros-api": "^0.0.59", "normalize.css": "^7.0.0", "react": "^16.2.0", "react-addons-css-transition-group": "^15.6.2", diff --git a/src/actions/notification.js b/src/actions/notification.js new file mode 100644 index 0000000..c671275 --- /dev/null +++ b/src/actions/notification.js @@ -0,0 +1,14 @@ +import { createActions } from '../utils/redux' + +/* Actions */ + +// Notifications +export const notifications = createActions('NOTIFICATIONS') + +// Notification +export const notification = createActions('NOTIFICATION') + +/* Action Creators */ + +// Notifications +export const fetchNotifications = () => ({ type: notifications.FETCH }) diff --git a/src/containers/home/index.js b/src/containers/home/index.js index 777ea15..3e82ba7 100644 --- a/src/containers/home/index.js +++ b/src/containers/home/index.js @@ -5,6 +5,8 @@ import { toastr } from 'react-redux-toastr' import * as walletSelectors from '../../reducers/wallet' import * as walletActions from '../../actions/wallet' +import * as notificationSelectors from '../../reducers/notification' +import * as notificationActions from '../../actions/notification' import * as arbitratorSelectors from '../../reducers/arbitrator' import * as arbitratorActions from '../../actions/arbitrator' import { RenderIf } from '../../utils/redux' @@ -29,11 +31,13 @@ class Home extends PureComponent { // Redux State accounts: walletSelectors.accountsShape.isRequired, balance: walletSelectors.balanceShape.isRequired, + notifications: notificationSelectors.notificationsShape.isRequired, PNKBalance: arbitratorSelectors.PNKBalanceShape.isRequired, arbitratorData: arbitratorSelectors.arbitratorDataShape.isRequired, // Action Dispatchers fetchBalance: PropTypes.func.isRequired, + fetchNotifications: PropTypes.func.isRequired, fetchPNKBalance: PropTypes.func.isRequired, activatePNK: PropTypes.func.isRequired, fetchArbitratorData: PropTypes.func.isRequired, @@ -44,8 +48,14 @@ class Home extends PureComponent { } componentDidMount() { - const { fetchBalance, fetchPNKBalance, fetchArbitratorData } = this.props + const { + fetchBalance, + fetchNotifications, + fetchPNKBalance, + fetchArbitratorData + } = this.props fetchBalance() + fetchNotifications() fetchPNKBalance() fetchArbitratorData() } @@ -80,7 +90,13 @@ class Home extends PureComponent { } render() { - const { accounts, balance, PNKBalance, arbitratorData } = this.props + const { + accounts, + balance, + notifications, + PNKBalance, + arbitratorData + } = this.props return (
@@ -186,21 +202,19 @@ class Home extends PureComponent {

Notifications

-
- -
-
- -
-
- -
-
- -
-
- -
+ } + done={ + notifications.data && + notifications.data.map(n => ( +
+ +
+ )) + } + failedLoading="There was an error fetching your notifications..." + />

Pending Actions

@@ -256,12 +270,14 @@ export default connect( state => ({ accounts: state.wallet.accounts, balance: state.wallet.balance, + notifications: state.notification.notifications, PNKBalance: state.arbitrator.PNKBalance, arbitratorData: state.arbitrator.arbitratorData, activatePNKFormIsInvalid: getActivatePNKFormIsInvalid(state) }), { fetchBalance: walletActions.fetchBalance, + fetchNotifications: notificationActions.fetchNotifications, fetchPNKBalance: arbitratorActions.fetchPNKBalance, activatePNK: arbitratorActions.activatePNK, fetchArbitratorData: arbitratorActions.fetchArbitratorData, diff --git a/src/reducers/index.js b/src/reducers/index.js index 028bf95..8314108 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -4,6 +4,7 @@ import { reducer as toastr } from 'react-redux-toastr' import { reducer as form } from 'redux-form' import wallet from './wallet' +import notification from './notification' import arbitrator from './arbitrator' import dispute from './dispute' @@ -13,6 +14,7 @@ export default combineReducers({ toastr, form, wallet, + notification, arbitrator, dispute }) diff --git a/src/reducers/notification.js b/src/reducers/notification.js new file mode 100644 index 0000000..50cf110 --- /dev/null +++ b/src/reducers/notification.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' + +import * as notificationActions from '../actions/notification' +import createReducer, { createResource } from '../utils/redux' + +// Shapes +const { + shape: notificationsShape, + initialState: notificationsInitialState +} = createResource(PropTypes.arrayOf(PropTypes.string)) +export { notificationsShape } + +// Reducer +export default createReducer( + { + notifications: notificationsInitialState + }, + { + [notificationActions.notification.RECEIVE]: (state, action) => ({ + ...state, + notifications: { + ...state.notifications, + data: [...state.notifications.data, action.payload.notification] + } + }) + } +) diff --git a/src/sagas/index.js b/src/sagas/index.js index 043e95f..713115f 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -3,6 +3,7 @@ import { delay } from 'redux-saga' import { spawn, call, all } from 'redux-saga/effects' import walletSaga from './wallet' +import notificationSaga from './notification' import arbitratorSaga from './arbitrator' import disputeSaga from './dispute' @@ -31,7 +32,12 @@ export function makeRestartable(saga) { } } -const rootSagas = [walletSaga, arbitratorSaga, disputeSaga].map(makeRestartable) +const rootSagas = [ + walletSaga, + notificationSaga, + arbitratorSaga, + disputeSaga +].map(makeRestartable) /** * The root saga. diff --git a/src/sagas/notification.js b/src/sagas/notification.js new file mode 100644 index 0000000..35156aa --- /dev/null +++ b/src/sagas/notification.js @@ -0,0 +1,83 @@ +import { eventChannel } from 'redux-saga' + +import { + fork, + take, + race, + takeLatest, + select, + call, + put +} from 'redux-saga/effects' + +import * as notificationActions from '../actions/notification' +import * as walletSelectors from '../reducers/wallet' +import * as walletActions from '../actions/wallet' +import { kleros, ARBITRATOR_ADDRESS } from '../bootstrap/dapp-api' +import { action, errorAction } from '../utils/action' + +/** + * Listens for push notifications. + */ +export function* pushNotificationsListener() { + // Start after fetching whole list of notifications + while (yield take(notificationActions.notifications.FETCH)) { + const account = yield select(walletSelectors.getAccount) // Current account + + // Set up event channel with subscriber + const channel = eventChannel(emitter => { + kleros.watchForEvents(ARBITRATOR_ADDRESS, account, notification => + emitter(notification) + ) + + return kleros.eventListener.clearArbitratorHandlers // Unsubscribe function + }) + + // Keep listening while on the same account + while (account === (yield select(walletSelectors.getAccount))) { + const [notification, accounts] = yield race([ + take(channel), // New notifications + take(walletActions.accounts.RECEIVE) // Accounts refetch + ]) + if (accounts) continue // Possible account change + + // Put new notification + yield put( + action(notificationActions.notification.RECEIVE, { notification }) + ) + } + + // We changed accounts, so close the channel. This calls unsubscribe under the hood which clears handlers for the old account + channel.close() + } +} + +/** + * Fetches the current account's notifications. + */ +export function* fetchNotifications() { + try { + const account = yield select(walletSelectors.getAccount) + const notifications = yield call( + kleros.notifications.getNotifications, + account + ) + + yield put( + action(notificationActions.notifications.RECEIVE, { notifications }) + ) + } catch (err) { + yield put(errorAction(notificationActions.notifications.FAIL_FETCH, err)) + } +} + +/** + * The root of the notification saga. + */ +export default function* notificationSaga() { + // Listeners + yield fork(pushNotificationsListener) + + // Notifications + yield takeLatest(notificationActions.notifications.FETCH, fetchNotifications) +} diff --git a/src/sagas/wallet.js b/src/sagas/wallet.js index 90ddf5c..a1e44f8 100644 --- a/src/sagas/wallet.js +++ b/src/sagas/wallet.js @@ -1,6 +1,6 @@ import unit from 'ethjs-unit' -import { takeLatest, call, put, select } from 'redux-saga/effects' +import { takeLatest, select, call, put } from 'redux-saga/effects' import * as walletSelectors from '../reducers/wallet' import * as walletActions from '../actions/wallet' diff --git a/yarn.lock b/yarn.lock index a6b71f9..c3d0839 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6548,9 +6548,9 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -kleros-api@^0.0.58: - version "0.0.58" - resolved "https://registry.yarnpkg.com/kleros-api/-/kleros-api-0.0.58.tgz#194a35f55309e49f29f28446dbddec65e97db4eb" +kleros-api@^0.0.59: + version "0.0.59" + resolved "https://registry.yarnpkg.com/kleros-api/-/kleros-api-0.0.59.tgz#045f4d0b88ec5346ab23305cc208e2f76f771fd0" dependencies: babel-plugin-transform-runtime "^6.23.0" babel-preset-env "^1.6.0"