Merge pull request #3216 from poanetwork/vb-token-transfers-pub-sub
Live update of token transfers at token and transaction pagespull/3220/head
commit
d552084321
@ -0,0 +1,87 @@ |
|||||||
|
import $ from 'jquery' |
||||||
|
import omit from 'lodash/omit' |
||||||
|
import URI from 'urijs' |
||||||
|
import humps from 'humps' |
||||||
|
import { subscribeChannel } from '../../socket' |
||||||
|
import { connectElements } from '../../lib/redux_helpers.js' |
||||||
|
import { createAsyncLoadStore } from '../../lib/async_listing_load' |
||||||
|
import '../address' |
||||||
|
|
||||||
|
export const initialState = { |
||||||
|
addressHash: null, |
||||||
|
channelDisconnected: false, |
||||||
|
filter: null |
||||||
|
} |
||||||
|
|
||||||
|
export function reducer (state, action) { |
||||||
|
switch (action.type) { |
||||||
|
case 'PAGE_LOAD': |
||||||
|
case 'ELEMENTS_LOAD': { |
||||||
|
return Object.assign({}, state, omit(action, 'type')) |
||||||
|
} |
||||||
|
case 'CHANNEL_DISCONNECTED': { |
||||||
|
if (state.beyondPageOne) return state |
||||||
|
|
||||||
|
return Object.assign({}, state, { channelDisconnected: true }) |
||||||
|
} |
||||||
|
case 'RECEIVED_NEW_TOKEN_TRANSFER': { |
||||||
|
if (state.channelDisconnected) return state |
||||||
|
|
||||||
|
if (state.beyondPageOne || |
||||||
|
(state.filter === 'to' && action.msg.toAddressHash !== state.addressHash) || |
||||||
|
(state.filter === 'from' && action.msg.fromAddressHash !== state.addressHash)) { |
||||||
|
return state |
||||||
|
} |
||||||
|
|
||||||
|
return Object.assign({}, state, { items: [action.msg.tokenTransferHtml, ...state.items] }) |
||||||
|
} |
||||||
|
case 'RECEIVED_NEW_REWARD': { |
||||||
|
if (state.channelDisconnected) return state |
||||||
|
|
||||||
|
return Object.assign({}, state, { items: [action.msg.rewardHtml, ...state.items] }) |
||||||
|
} |
||||||
|
default: |
||||||
|
return state |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const elements = { |
||||||
|
'[data-selector="channel-disconnected-message"]': { |
||||||
|
render ($el, state) { |
||||||
|
if (state.channelDisconnected) $el.show() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($('[data-page="address-token-transfers"]').length) { |
||||||
|
const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') |
||||||
|
const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash |
||||||
|
const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true)) |
||||||
|
|
||||||
|
connectElements({ store, elements }) |
||||||
|
|
||||||
|
store.dispatch({ |
||||||
|
type: 'PAGE_LOAD', |
||||||
|
addressHash, |
||||||
|
filter, |
||||||
|
beyondPageOne: !!blockNumber |
||||||
|
}) |
||||||
|
|
||||||
|
const addressChannel = subscribeChannel(`addresses:${addressHash}`) |
||||||
|
addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) |
||||||
|
addressChannel.on('token_transfer', (msg) => { |
||||||
|
store.dispatch({ |
||||||
|
type: 'RECEIVED_NEW_TOKEN_TRANSFER', |
||||||
|
msg: humps.camelizeKeys(msg) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
const rewardsChannel = subscribeChannel(`rewards:${addressHash}`) |
||||||
|
rewardsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) |
||||||
|
rewardsChannel.on('new_reward', (msg) => { |
||||||
|
store.dispatch({ |
||||||
|
type: 'RECEIVED_NEW_REWARD', |
||||||
|
msg: humps.camelizeKeys(msg) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
import $ from 'jquery' |
||||||
|
import omit from 'lodash/omit' |
||||||
|
import URI from 'urijs' |
||||||
|
import humps from 'humps' |
||||||
|
import { subscribeChannel } from '../../socket' |
||||||
|
import { connectElements } from '../../lib/redux_helpers.js' |
||||||
|
import { createAsyncLoadStore } from '../../lib/async_listing_load' |
||||||
|
import '../token_counters' |
||||||
|
|
||||||
|
export const initialState = { |
||||||
|
addressHash: null, |
||||||
|
channelDisconnected: false |
||||||
|
} |
||||||
|
|
||||||
|
export function reducer (state, action) { |
||||||
|
switch (action.type) { |
||||||
|
case 'PAGE_LOAD': |
||||||
|
case 'ELEMENTS_LOAD': { |
||||||
|
return Object.assign({}, state, omit(action, 'type')) |
||||||
|
} |
||||||
|
case 'CHANNEL_DISCONNECTED': { |
||||||
|
if (state.beyondPageOne) return state |
||||||
|
|
||||||
|
return Object.assign({}, state, { channelDisconnected: true }) |
||||||
|
} |
||||||
|
case 'RECEIVED_NEW_TOKEN_TRANSFER': { |
||||||
|
if (state.channelDisconnected) return state |
||||||
|
|
||||||
|
if (state.beyondPageOne) { |
||||||
|
return state |
||||||
|
} |
||||||
|
|
||||||
|
return Object.assign({}, state, { items: [action.msg.tokenTransferHtml, ...state.items] }) |
||||||
|
} |
||||||
|
case 'RECEIVED_NEW_REWARD': { |
||||||
|
if (state.channelDisconnected) return state |
||||||
|
|
||||||
|
return Object.assign({}, state, { items: [action.msg.rewardHtml, ...state.items] }) |
||||||
|
} |
||||||
|
default: |
||||||
|
return state |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const elements = { |
||||||
|
'[data-selector="channel-disconnected-message"]': { |
||||||
|
render ($el, state) { |
||||||
|
if (state.channelDisconnected) $el.show() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($('[data-page="token-transfer-list"]')) { |
||||||
|
const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') |
||||||
|
const addressHash = $('[data-page="token-details"]')[0].dataset.pageAddressHash |
||||||
|
const { blockNumber } = humps.camelizeKeys(URI(window.location).query(true)) |
||||||
|
|
||||||
|
connectElements({ store, elements }) |
||||||
|
|
||||||
|
store.dispatch({ |
||||||
|
type: 'PAGE_LOAD', |
||||||
|
addressHash, |
||||||
|
beyondPageOne: !!blockNumber |
||||||
|
}) |
||||||
|
|
||||||
|
const tokensChannel = subscribeChannel(`tokens:${addressHash}`) |
||||||
|
tokensChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) |
||||||
|
tokensChannel.on('token_transfer', (msg) => { |
||||||
|
store.dispatch({ |
||||||
|
type: 'RECEIVED_NEW_TOKEN_TRANSFER', |
||||||
|
msg: humps.camelizeKeys(msg) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
const rewardsChannel = subscribeChannel(`rewards:${addressHash}`) |
||||||
|
rewardsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) |
||||||
|
rewardsChannel.on('new_reward', (msg) => { |
||||||
|
store.dispatch({ |
||||||
|
type: 'RECEIVED_NEW_REWARD', |
||||||
|
msg: humps.camelizeKeys(msg) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
defmodule BlockScoutWeb.TokenChannel do |
||||||
|
@moduledoc """ |
||||||
|
Establishes pub/sub channel for live updates of token transfer events. |
||||||
|
""" |
||||||
|
use BlockScoutWeb, :channel |
||||||
|
|
||||||
|
alias BlockScoutWeb.Tokens.TransferView |
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.Chain.Hash |
||||||
|
alias Phoenix.View |
||||||
|
|
||||||
|
intercept(["token_transfer"]) |
||||||
|
|
||||||
|
{:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") |
||||||
|
@burn_address_hash burn_address_hash |
||||||
|
|
||||||
|
def join("tokens:new_token_transfer", _params, socket) do |
||||||
|
{:ok, %{}, socket} |
||||||
|
end |
||||||
|
|
||||||
|
def join("tokens:" <> _transaction_hash, _params, socket) do |
||||||
|
{:ok, %{}, socket} |
||||||
|
end |
||||||
|
|
||||||
|
def handle_out("token_transfer", %{token_transfer: token_transfer}, socket) do |
||||||
|
Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) |
||||||
|
|
||||||
|
rendered_token_transfer = |
||||||
|
View.render_to_string( |
||||||
|
TransferView, |
||||||
|
"_token_transfer.html", |
||||||
|
conn: socket, |
||||||
|
token: token_transfer.token, |
||||||
|
token_transfer: token_transfer, |
||||||
|
burn_address_hash: @burn_address_hash |
||||||
|
) |
||||||
|
|
||||||
|
push(socket, "token_transfer", %{ |
||||||
|
token_transfer_hash: Hash.to_string(token_transfer.transaction_hash), |
||||||
|
token_transfer_html: rendered_token_transfer |
||||||
|
}) |
||||||
|
|
||||||
|
{:noreply, socket} |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue