diff --git a/js/package.json b/js/package.json index 6f8693b39..63472f9b8 100644 --- a/js/package.json +++ b/js/package.json @@ -135,6 +135,7 @@ "blockies": "0.0.2", "brace": "0.9.0", "bytes": "2.4.0", + "debounce": "1.0.0", "es6-error": "4.0.0", "es6-promise": "4.0.5", "ethereumjs-tx": "1.1.4", diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js index a86f9e4b1..941164b1c 100644 --- a/js/src/modals/Verification/email-store.js +++ b/js/src/modals/Verification/email-store.js @@ -23,7 +23,7 @@ import VerificationStore, { } from './store'; import { postToServer } from '../../3rdparty/email-verification'; -const EMAIL_VERIFICATION = 4; // id in the `BadgeReg.sol` contract +const EMAIL_VERIFICATION = 7; // id in the `BadgeReg.sol` contract export default class EmailVerificationStore extends VerificationStore { @observable email = ''; diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 6e4b898d0..0bd2e5157 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -15,12 +15,41 @@ // along with Parity. If not, see . import { uniq } from 'lodash'; +import debounce from 'debounce'; import ABI from '~/contracts/abi/certifier.json'; import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { addCertification, removeCertification } from './actions'; +// TODO: move this to a more general place +const updatableFilter = (api, onFilter) => { + let filter = null; + + const update = (address, topics) => { + if (filter) { + filter = filter.then((filterId) => { + api.eth.uninstallFilter(filterId); + }); + } + filter = (filter || Promise.resolve()) + .then(() => api.eth.newFilter({ + fromBlock: 0, + toBlock: 'latest', + address, + topics + })) + .then((filterId) => { + onFilter(filterId); + return filterId; + }) + .catch((err) => { + console.error('Failed to create certifications filter:', err); + }); + }; + return update; +}; + export default class CertificationsMiddleware { toMiddleware () { const api = Contracts.get()._api; @@ -29,75 +58,96 @@ export default class CertificationsMiddleware { const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); const Revoked = contract.events.find((e) => e.name === 'Revoked'); - let certifiers = []; - let accounts = []; // these are addresses - - const fetchConfirmedEvents = (dispatch) => { - if (certifiers.length === 0 || accounts.length === 0) return; - api.eth.getLogs({ - fromBlock: 0, - toBlock: 'latest', - address: certifiers.map((c) => c.address), - topics: [ [ Confirmed.signature, Revoked.signature ], accounts ] - }) - .then((logs) => contract.parseEventLogs(logs)) - .then((logs) => { - logs.forEach((log) => { - const certifier = certifiers.find((c) => c.address === log.address); - if (!certifier) { - throw new Error(`Could not find certifier at ${log.address}.`); - } - const { id, name, title, icon } = certifier; - - if (log.event === 'Revoked') { - dispatch(removeCertification(log.params.who.value, id)); - } else { - dispatch(addCertification(log.params.who.value, id, name, title, icon)); - } - }); - }) - .catch((err) => { - console.error('Failed to fetch Confirmed events:', err); - }); - }; - - return (store) => (next) => (action) => { - switch (action.type) { - case 'fetchCertifiers': - badgeReg.certifierCount().then((count) => { - new Array(+count).fill(null).forEach((_, id) => { - badgeReg.fetchCertifier(id) - .then((cert) => { - if (!certifiers.some((c) => c.id === cert.id)) { - certifiers = certifiers.concat(cert); - fetchConfirmedEvents(store.dispatch); - } - }) - .catch((err) => { - console.warn(`Could not fetch certifier ${id}:`, err); - }); - }); - }); - - break; - case 'fetchCertifications': - const { address } = action; - - if (!accounts.includes(address)) { - accounts = accounts.concat(address); - fetchConfirmedEvents(store.dispatch); + return (store) => { + const onLogs = (logs) => { + logs = contract.parseEventLogs(logs); + logs.forEach((log) => { + const certifier = certifiers.find((c) => c.address === log.address); + if (!certifier) { + throw new Error(`Could not find certifier at ${log.address}.`); } + const { id, name, title, icon } = certifier; - break; - case 'setVisibleAccounts': - const { addresses } = action; - accounts = uniq(accounts.concat(addresses)); - fetchConfirmedEvents(store.dispatch); + if (log.event === 'Revoked') { + store.dispatch(removeCertification(log.params.who.value, id)); + } else { + store.dispatch(addCertification(log.params.who.value, id, name, title, icon)); + } + }); + }; - break; - default: - next(action); - } + let filter = null; + + const onFilter = (filterId) => { + filter = filterId; + api.eth.getFilterLogs(filterId) + .then(onLogs) + .catch((err) => { + console.error('Failed to fetch certifier events:', err); + }); + }; + + const fetchChanges = debounce(() => { + api.eth.getFilterChanges(filter) + .then(onLogs) + .catch((err) => { + console.error('Failed to fetch new certifier events:', err); + }); + }, 10 * 1000, true); + api.subscribe('eth_blockNumber', (err) => { + if (err) return; + fetchChanges(); + }); + + const updateFilter = updatableFilter(api, onFilter); + let certifiers = []; + let accounts = []; // these are addresses + + const fetchConfirmedEvents = () => { + updateFilter(certifiers.map((c) => c.address), [ + [ Confirmed.signature, Revoked.signature ], + accounts + ]); + }; + + return (next) => (action) => { + switch (action.type) { + case 'fetchCertifiers': + badgeReg.certifierCount().then((count) => { + new Array(+count).fill(null).forEach((_, id) => { + badgeReg.fetchCertifier(id) + .then((cert) => { + if (!certifiers.some((c) => c.id === cert.id)) { + certifiers = certifiers.concat(cert); + fetchConfirmedEvents(); + } + }) + .catch((err) => { + console.warn(`Could not fetch certifier ${id}:`, err); + }); + }); + }); + + break; + case 'fetchCertifications': + const { address } = action; + + if (!accounts.includes(address)) { + accounts = accounts.concat(address); + fetchConfirmedEvents(); + } + + break; + case 'setVisibleAccounts': + const { addresses } = action; + accounts = uniq(accounts.concat(addresses)); + fetchConfirmedEvents(); + + break; + default: + next(action); + } + }; }; } } diff --git a/js/src/ui/Certifications/certifications.css b/js/src/ui/Certifications/certifications.css index 077830e3a..bff45ce5f 100644 --- a/js/src/ui/Certifications/certifications.css +++ b/js/src/ui/Certifications/certifications.css @@ -22,6 +22,7 @@ .certification { display: inline-block; position: relative; + margin-top: 1em; margin-right: .5em; padding: .3em .6em .2em 2.6em; border-radius: 1em;