- README: add Frontend section, deploy script, docs links, status - docs/README.md: new documentation overview (entry points, frontend, deployment) - docs/EXPLORER_API_ACCESS.md: reference deploy-frontend-to-vmid5000.sh for frontend-only deploy - docs/INDEX.md: add Frontend & Explorer section, fix Quick Start, Last Updated - README_DEPLOYMENT: add docs/README, EXPLORER_API_ACCESS, deploy script, deployment guide - frontend/FRONTEND_REVIEW.md: add post-review update (C1–L4 implemented) Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
11 KiB
Frontend Code Review – Explorer Monorepo
Scope: explorer-monorepo/frontend/
Reviewed: Vanilla JS SPA (public/index.html), React/Next.js app (src/), services, config.
Date: 2025-02-09
Update (post-review): All "Improve" items from this review were implemented per FRONTEND_TASKS_AND_REVIEW.md: response.ok checks (C1–C3), blocks API normalizer (C4/M4), escapeHtml/safe URLs/onclick (H1–H3), getRpcUrl in rpcCall (H4), cancel blocks rAF (H5), block number validation (M1), stable list keys (M2), named constants (M3), shared block card helper (L1), DEBUG console gating (L2), aria-live for errors (L3), API modules (L4). See that file for the full task list and status.
1. Overview
The frontend has two delivery paths:
| Asset | Purpose | Deployed |
|---|---|---|
public/index.html |
Single-page explorer (Blocks, Transactions, Bridge, WETH, search, wallet connect). Vanilla JS, ~4.2k lines. | Yes (VMID 5000, https://explorer.d-bis.org) |
src/ (Next.js) |
React app (blocks, transactions, addresses, search, wallet). Uses Blockscout-style API. | No (dev/build only) |
2. Vanilla JS SPA (public/index.html)
2.1 Security
Good:
escapeHtml()is used for error messages, revert reasons, method names, ABI/bytecode, token names/symbols, NFT metadata, and other user/API-derived strings beforeinnerHTML. Reduces XSS from API or user input.- CSP in
<meta>restricts script/style/font/connect sources; comment documentsunsafe-evalfor ethers v5 UMD. - Credentials:
fetchAPIusescredentials: 'omit'for API calls. - Wallet: No private keys or secrets in code; MetaMask/ethers used for signing.
Improve:
- Defense in depth for hashes/addresses: Any API-derived string (e.g.
hash,from,to,address) that is interpolated intoinnerHTMLshould be escaped. Currently:- Block cards:
shortenHash(hash)and block number are injected withoutescapeHtml. Block numbers are numeric; hashes are usually hex from a trusted API, but escaping would harden against a compromised or malicious API. - Breadcrumbs:
shortenHash(identifier)andidentifierinhref="#/address/' + identifier + '"– ifidentifiercan contain'or", it could break attributes or enable injection. RecommendescapeHtml(shortenHash(hash))for display and sanitize/validate for attributes.
- Block cards:
shortenHash: Only truncates; does not strip HTML. UseescapeHtml(shortenHash(hash))wherever the result is used in HTML.
2.2 Correctness & Robustness
Good:
- Navigation: Re-entrancy guard (
_inNavHandler) and “showView first, then set hash” prevent the previous infinite recursion from hashchange. applyHashRoute: UsescurrentViewandcurrentDetailKeyso the same view/detail is not re-applied unnecessarily.fetchAPI: UsesAbortControllerand 15s timeout;AbortErroris handled in retry logic.fetchAPIWithRetry: Exponential backoff; retries on timeout/5xx/network; does not retry on non-retryable errors.- RPC fallback: When Blockscout API fails, blocks/transactions can be loaded via
rpcCall(e.g.eth_blockNumber,eth_getBlockByNumber). - Validation:
safeBlockNumber,safeTxHash,safeAddressused before detail views and in retry buttons. - Wrap/Unwrap: Balance checks and
callStaticsimulation before deposit/withdraw; user rejection vs contract error distinguished in messages.
Improve:
rpcCall: Uses a single RPC URL; does not usegetRpcUrl()for failover. Consider usinggetRpcUrl()for critical RPC calls so the app benefits from the same failover as elsewhere.- Memory/cleanup:
requestAnimationFrame(animateScroll)in the blocks scroll runs indefinitely. On view change, the loop is not cancelled; consider storing the frame id and cancelling in a cleanup when leaving the blocks view. - Breadcrumb
identifier: InupdateBreadcrumb,identifieris used inhref="#/address/' + identifier + '". Ifidentifiercontains', the attribute can break. Prefer escaping or usingencodeURIComponentfor path segments.
2.3 Structure & Maintainability
- Single file: ~4.2k lines in one HTML file makes navigation and testing harder. Consider splitting script into logical modules (e.g. API, nav, views, wallet) and bundling, or at least grouping related functions and marking sections.
- Duplicate logic: Block card HTML is built in multiple places (home stats area, blocks list, block detail). A single
createBlockCard-style helper (or shared template) would reduce drift and bugs. - Magic numbers: Timeouts (15s, 5s), retry counts (3), and delays are literal; consider named constants at the top of the script.
- Console: Several
console.log/console.warn/console.errorcalls are useful for debugging but could be gated by aDEBUGflag or removed for production if desired.
2.4 Accessibility & UX
- Nav links use
onclickandaria-labelwhere checked; focus and keyboard flow should be verified (e.g. tab order, Enter to activate). - Error messages and retry buttons are visible; consider ensuring they are announced (e.g. live region) for screen readers.
- Dark theme is supported and persisted in
localStorage.
3. React/Next.js App (src/)
3.1 Security
- React escaping: Components render props as text by default, so no raw
dangerouslySetInnerHTMLwas found; Address, Card, Table, etc. are safe from XSS in normal use. - API client: Uses
localStorage.getItem('api_key')forX-API-Key; ensure key is not exposed in logs or error messages. - Env:
NEXT_PUBLIC_*is appropriate for client-side config; no secrets in frontend code.
3.2 Data Fetching & API Shape
Issues:
addresses/[address].tsx: Usesfetch()thenresponse.json()without checkingresponse.ok. On 4xx/5xx,datamay be an error body andsetAddressInfo(data.data)can set invalid state. Recommend: checkresponse.ok, handle errors, and only set state on success.blocksApi.list: ReturnsApiResponse<Block[]>(expects{ data: Block[] }). If the backend returns a different shape (e.g.{ items: [] }),response.datamay beundefinedandsetBlocks(response.data)can lead to runtime errors inblocks.map(). Align client with actual API response or normalize in the client.- Home page:
loadRecentBlocks()usesblocksApi.list; same shape assumption as above.loadStats()is a placeholder (no real API call).
3.3 Correctness
- useEffect deps: In
page.tsx,useEffect(..., [])callsloadStatsandloadRecentBlockswhich are recreated each render. This is fine with empty deps (run once). Inblocks/index.tsxandtransactions/index.tsx, deps are[page];loadBlocks/loadTransactionsare not in the dependency array, which is intentional to avoid unnecessary runs; no bug found. - Block detail:
blocks/[number].tsxusesparseInt((params?.number as string) ?? '0')– invalid or missing number becomesNaN/0; the API may return 404. Consider validating and showing a clear “Invalid block number” message. - Table key:
Table.tsxuseskey={rowIndex}; if the list is reordered or filtered, prefer a stable key (e.g.block.number,tx.hash).
3.4 Consistency & Gaps
- Routing: Next.js app uses file-based routes (
/blocks,/transactions,/addresses/[address]). The deployed SPA uses hash routing (#/blocks,#/address/0x...). They are separate; no conflict, but be aware that deep links differ between the two frontends. - API base: React app uses
NEXT_PUBLIC_API_URL(defaulthttp://localhost:8080). The vanilla SPA uses same-origin/apior Blockscout API. Ensure backend and env are aligned when running the Next app against a real API. - blocks API: Only blocks are implemented in
services/api/; transactions and addresses use rawfetchin pages. Consider moving to shared API modules and the same client for consistency and error handling.
4. Services & Config
4.1 API Client (src/services/api/client.ts)
- Axios instance with timeout (30s), JSON headers, and optional
X-API-KeyfromlocalStorage. - Response interceptor rejects with
error.response.data; type isApiError. Callers should handle both API error shape and network errors. - Note: Used by blocks API only; other pages use
fetchdirectly.
4.2 Blocks API (src/services/api/blocks.ts)
- Builds query params correctly; uses
apiClient.get<Block[]>. - Return type
ApiResponse<Block[]>assumes backend returns{ data: T }. If backend is Blockscout-style (items, etc.), either add an adapter or document the expected backend contract.
4.3 Next.js Config
reactStrictMode: true,output: 'standalone'.NEXT_PUBLIC_API_URLandNEXT_PUBLIC_CHAIN_IDdefaulted innext.config.js; can be overridden by env.
5. Recommendations Summary
| Priority | Item | Action |
|---|---|---|
| P1 | Address page fetch | In addresses/[address].tsx, check response.ok and handle non-2xx before parsing JSON and setting state. |
| P1 | API response shape | Confirm backend response shape for blocks (and any shared APIs). Normalize to { data } in client or document and handle items/other shapes. |
| P2 | XSS hardening (SPA) | Use escapeHtml(shortenHash(hash)) (and escape other API-derived strings) wherever content is written with innerHTML. Escape or encode identifier in breadcrumb href. |
| P2 | RPC failover | Use getRpcUrl() inside rpcCall() (or for critical paths) so RPC failover is consistent. |
| P2 | Blocks scroll animation | Cancel requestAnimationFrame when leaving the blocks view (e.g. in a cleanup or when switching view). |
| P3 | SPA structure | Split script into modules or clearly grouped sections; extract shared constants and block-card markup. |
| P3 | Table keys | Use stable keys (e.g. block.number, tx.hash) in list components instead of index. |
| P3 | Block number validation | In blocks/[number].tsx, validate params.number and show a clear message for invalid or missing block number. |
6. Files Reviewed
public/index.html– full read and grep for escapeHtml, innerHTML, fetch, navigation, wallet.src/app/layout.tsx,src/app/page.tsx,src/app/wallet/page.tsxsrc/pages/_app.tsx,src/pages/blocks/index.tsx,src/pages/blocks/[number].tsx,src/pages/transactions/index.tsx,src/pages/transactions/[hash].tsx,src/pages/addresses/[address].tsx,src/pages/search/index.tsxsrc/components/common/Card.tsx,Button.tsx,Table.tsxsrc/components/blockchain/Address.tsx,src/components/wallet/AddToMetaMask.tsxsrc/services/api/client.ts,src/services/api/blocks.tspackage.json,next.config.js