mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
fix(home): map the realtime block onto the API shape before it reaches the list
The home page rendered the error boundary — "Oops! Something went wrong" —
within seconds of loading, on every explorer in the estate, with no interaction
at all:
TypeError: Cannot read properties of undefined (reading 'length')
at HashStringShorten (ui/shared/HashStringShorten.tsx:17)
The list on the home page is filled from the REST API and then kept current by
the realtime stream, and the two do not agree on what a block looks like.
/main-page/blocks answers with the API shape — `height`, and `miner` as an
address object. The indexer broadcasts a raw EVM block:
data: {"event":"blocks","chain":"cchain","data":{
"number":1090152,"miner":"0x0100...","txCount":4, ... }}
`new_block` put that straight into the same query cache, so LatestBlocksItem
handed `AddressEntity` a bare string. `address.hash` on a string is undefined,
the entity passed undefined down as its text, and HashStringShorten read
`.length` off it. Instrumented in a dev build: the four blocks that arrive over
the stream log `miner` as a string with no `height`, against six from REST that
carry both — and exactly four entities then render with no text.
TRANSLATIONS already exists as the one place a backend broadcast becomes app
data, and it already has a transform hook, so the shape is settled there rather
than at each reader. Everything downstream keeps knowing exactly one block
shape, which is what it already assumed.
The spec asserted the raw passthrough, so it encoded the bug; it now asserts
the mapped shape and covers `miner` specifically.
This commit is contained in:
+13
-2
@@ -101,10 +101,21 @@ describe('SSE Socket — envelope routing', () => {
|
||||
channel.on('new_block', handler);
|
||||
|
||||
lastEs().emitOpen();
|
||||
lastEs().emitMessage({ event: 'blocks', chain: 'cchain', data: { number: 42, hash: '0xabc' } });
|
||||
lastEs().emitMessage({ event: 'blocks', chain: 'cchain', data: { number: 42, hash: '0xabc', miner: '0x01', txCount: 3 } });
|
||||
|
||||
// the broadcast is a raw EVM block; it reaches the UI in the API shape,
|
||||
// because it lands in the same list as blocks fetched over REST. Handing
|
||||
// `miner` through as a bare string blanked the whole home page.
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
expect(handler).toHaveBeenCalledWith({ block: { number: 42, hash: '0xabc' } });
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
block: expect.objectContaining({
|
||||
height: 42,
|
||||
hash: '0xabc',
|
||||
miner: { hash: '0x01' },
|
||||
transactions_count: 3,
|
||||
type: 'block',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('routes a topic-scoped envelope straight to that channel + event', () => {
|
||||
|
||||
+19
-1
@@ -63,9 +63,27 @@ interface Translation {
|
||||
// evm/indexer.go `BroadcastBlock`). Extend here as the backend grows new
|
||||
// GLOBAL broadcast types; SCOPED events (addresses:*, tokens:*, …) arrive
|
||||
// with an explicit `topic` and need no entry.
|
||||
// The indexer broadcasts a raw EVM block — `number`, and `miner` as a bare
|
||||
// address string. The blocks the UI already holds came from the REST API and
|
||||
// are shaped the other way: `height`, and `miner` as an address object. Both
|
||||
// end up in one list, so the raw block is mapped here, the one place a
|
||||
// broadcast becomes app data, and nothing downstream has to know two shapes.
|
||||
function toApiBlock(data: Payload): Payload {
|
||||
const block = data as Record<string, unknown>;
|
||||
|
||||
return {
|
||||
...block,
|
||||
height: block.number,
|
||||
miner: { hash: block.miner },
|
||||
transactions_count: block.txCount,
|
||||
rewards: [],
|
||||
type: 'block',
|
||||
};
|
||||
}
|
||||
|
||||
const TRANSLATIONS: Record<string, Array<Translation>> = {
|
||||
blocks: [
|
||||
{ topic: 'blocks:new_block', event: 'new_block', transform: (block) => ({ block }) },
|
||||
{ topic: 'blocks:new_block', event: 'new_block', transform: (block) => ({ block: toApiBlock(block) }) },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user