Skip to content

Explorer HTML v0.9: Refactoring to async fetch - #2080

Merged
Maxnflaxl merged 14 commits into
BeamMW:masterfrom
dbadol:Explorer_HTM
Aug 20, 2026
Merged

Explorer HTML v0.9: Refactoring to async fetch#2080
Maxnflaxl merged 14 commits into
BeamMW:masterfrom
dbadol:Explorer_HTM

Conversation

@dbadol

@dbadol dbadol commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Refactoring to a "single page app" using async fetch to avoid HTML reloads.
See details of changes in commits.
A PR is made for it alone because it significantly changes the code in many places.

dbadol added 7 commits July 25, 2026 00:09
Refactoring to avoid constant reloads of the HTML file:
- Create showPage() function to query the node, update the URL parameters (without reload) and call the data display functions.
- Replace 'xmlhttp' query logic with async 'fetch'
- Replace links in menus and search with calls to showPage().
- Change UrlSelf() to use showPage().
- Change MakeLinkToOlderBlocks() and MakeLinkToNewerBlocks() to use UrlSelf().
- Change UrlBlock() to use the 'adj' parameter (for PoS chains).
- Rename columnHeaders* variables to allow switching between PoW and PoS vocabulary.
- Remove initialization logic of global parameters as they will now be set (and updated) from within showPage().
- Add pageConfig and paramConfig global variables to define the characteristics of all pages and all query parameters (including their type).
- Rename urlPrefix as g_urlPrefix.
@Maxnflaxl

Maxnflaxl commented Aug 18, 2026

Copy link
Copy Markdown
Member

Reviewed the refactor — the direction is good: pageConfig/paramConfig replace a sprawling if/else chain with something declarative, and moving off XMLHttpRequest/window.location reloads makes the whole thing much nicer to navigate. A few things I think need fixing before this lands.

✅ 1. columnHeaders aliases columnHeaders_pow, so switching to PoS corrupts it permanently

let columnHeaders = columnHeaders_pow;   // line 3067
...
columnHeaders[code] = (g_IsPos) ? columnHeaders_pos[code] : columnHeaders_pow[code];   // line 3400

These are the same object, so the assignment on line 3400 writes into columnHeaders_pow.

Load on mainnet (PoW), switch the dropdown to a PoS network: the five shared codes (h, N, g, d, D) in columnHeaders_pow are overwritten with the PoS entries. Switch back to mainnet: the restore branch reads columnHeaders_pow[code], which now is the PoS entry, so nothing is restored.

Beyond the wrong labels, Obj2Html matches on .original (line 2170). A PoW node returns Age/Difficulty, but the table now expects d.Age/Difficulty, so those headers get no data-code and the column/graph features break until a full page reload — which the SPA refactor no longer does.

let columnHeaders = { ...columnHeaders_pow };

Fixed in ed1a8aab6c8958.

✅ 2. An unknown ?type= hangs the app on "Loading…"

const activeParams = pageConfig[page_id].params || [];   // line 3414

This is outside the try that starts at line 3487. Open ?network=mainnet&type=blocks (still present, commented out, at line 3262) or any stale bookmark / typo, and pageConfig[page_id] is undefined → uncaught TypeError. The catch never runs, the URL is never rewritten, and MainContent sits on the loading spinner forever. The old code's final else defaulted g_type to 'status'; that fallback is worth keeping:

const page = pageConfig[page_id] || (page_id = 'status', pageConfig['status']);
const activeParams = page.params || [];

Fixed in ed1a8aab6c8958.

✅ 3. UrlSelf() escapes " but not & — JS injection into the javascript: href

const text = JSON.stringify(params).replace(/"/g, '"');   // line 1986

Any value that already contains the literal text " round-trips: the browser HTML-decodes it back into a real quote, which closes the JS string literal.

It's reachable. cols is an unsanitised string param that also gets persisted to localStorage:

  1. Visit ?network=mainnet&type=hdrs&cols=x%26quot%3B});alert(1)//
  2. g_cols becomes x"});alert(1)// and is cached
  3. MakeLinkToOlderBlocks() reads it back out of window.location.search into params
  4. The href becomes javascript:showPage('hdrs', {"cols":"x"});alert(1)//"});
  5. After HTML decoding: showPage('hdrs', {"cols":"x"});alert(1)//"});
  6. Clicking "Previous blocks…" executes it — and because cols is persisted, it survives later visits

Minimum fix is to escape & before ". Better would be to drop the javascript: hrefs entirely in favour of real hrefs plus a delegated click handler reading data-* attributes — that also restores middle-click / "open in new tab", which the current links lose.

Fixed in ed1a8aab6c8958.

✅ 4. No request-generation guard — a slow response can render over a newer page

showPage() has no cancellation. Click "Block Headers" (slow, ~100 rows) then immediately "Confidential Assets": the second call overwrites the globals and pushes type=assets, assets render, then the first fetch resolves and DisplayHdrs paints a header table while the URL and globals say assets. Same thing happens holding down the Back button.

let g_requestId = 0;
async function showPage(page_id, parameters = {}) {
  const myId = ++g_requestId;
  ...
  const data = await fetchAPI(fetchType, fetchParams);
  if (myId !== g_requestId) return;   // superseded
  page.display.call({ responseText: JSON.stringify(data) });

Fixed in ed1a8aab6c8958.

✅ 5. Swap-totals success path dereferences a possibly-removed div

document.getElementById('divSwapTotals').innerHTML = text;        // line 2911
document.getElementById('divAssetSwapTotals').innerHTML = text;   // line 2976

The new .catch handlers correctly guard with if (el), but the success paths don't. Open Atomic Swaps, then click another menu item before the second swap_totals request returns: MainContent has already been replaced, so this throws Cannot set properties of null. Under the old full-reload model the document was torn down, so it couldn't happen. Same generation guard as #4 would cover both.

Fixed in ed1a8aab6c8958.

Two smaller things

  • ✅ The new .catch handlers emit <p class="error">, but the only error style in the sheet is h2.errorMessage (line 435), so that message renders unstyled.
  • ✅ The try in showPage() also wraps page.display.call(...), so a rendering bug in any Display* function is now reported to the user as "Error Loading Data" — a network failure and a JS bug become indistinguishable, and console.error is the only clue. Consider awaiting the fetch inside the try and calling the display function after it.

Edited to mark resolved items. All of the above were fixed in ed1a8aab6c8958. The points still outstanding are in the follow-up comment.

dbadol added 6 commits August 19, 2026 01:44
- Add style for class 'p.errorMessage'.
- Encode '&' in javascript links, for security.
- Copy object content instead of creating an alias for 'columnHeaders'.
- Add a showPage counter to avoid mixing fetched data when switching pages too fast.
- Default unknown 'type=' in URL to 'status'
- Tests done: 'replaceState' and 'pushState' work well when loading the app locally through 'file://"
- Ensure the div containers still exist before displaying Atomic or Asset Swap totals.
- In showPage(), call the display function *after* the fetch try, so that any display error remains distinguishable from network errors.
@Maxnflaxl

Maxnflaxl commented Aug 20, 2026

Copy link
Copy Markdown
Member

Went through the six new commits (ed1a8aab6c8958). I checked each fix from the previous round rather than taking the commit messages for it — all of them are correct, bar the one gap noted below.

Also worth saying: the popup.dataset.hasBackdropListener fix in openDialog() is correct — dataset coerces to the string "true", which is truthy, so the guard does what it looks like it does. And the whole file still parses clean with no duplicate top-level declarations across the script blocks.

Three things left — all three since fixed in bc5d819. I verified both of the behavioural ones in a browser harness rather than reasoning about them, both when reporting and after the fix.

✅ 1. The generation guard covers only the success path

} catch (err) {
  console.error('Failed to load page ' + page_id + ':', err);
  document.getElementById('MainContent').innerHTML = `…`;   // line 3577
  return;
}

// If another page has already been called during fetch, interrupt this old one
if (showPageCounter !== g_showPageCounter) { return; }      // line 3587

The check at 3587 is after the catch returns, so a stale failing request is not guarded.

Reproduced by stalling **/hdrs* for 2.5 s and then aborting it, calling showPage('hdrs') and 50 ms later showPage('status'):

before hdrs fails: "Blockchain status"
after  hdrs fails: h2    = "Error Loading Data"
                   url   = ?network=mainnet&type=status
                   retry = javascript:showPage('hdrs', { replaceState: true });

The status page renders correctly, then the dead hdrs request overwrites it with an error screen whose Retry link points at hdrs while the URL and every global still say status. Moving the check to the top of the catch fixes it:

} catch (err) {
  if (showPageCounter !== g_showPageCounter) { return; }
  console.error();
  
}

Fixed in bc5d819, and re-tested. Same scenario now ends on "Blockchain status" with the URL still ?network=mainnet&type=status — the dead request is dropped silently. I also checked the guard doesn't over-suppress: a lone failing hdrs request still renders "Error Loading Data" as it should.

✅ 2. Narrowing the try moved page.display.call() outside any handler

if (showPageCounter !== g_showPageCounter) { return; }
page.display.call({ responseText: JSON.stringify(data) });   // line 3589 — unguarded

Narrowing the try was the right call, but an exception thrown while rendering is now an unhandled promise rejection and the spinner is never replaced. Substituting a Display function that throws:

stillLoading: true      h2: "Loading..."      pageerror: "boom: unexpected node shape"

It stays that way indefinitely. This is reachable for real — DisplayBlock does j['value'][0][1]['value'] at line 2494 when a kernel search comes back in an unexpected shape. Before this change it at least rendered something. A second try around the display call, with its own message ("Error displaying data" rather than "Error Loading Data"), keeps the two failure modes distinguishable without going back to conflating them.

Fixed in bc5d819, and re-tested: the same throwing Display function now yields stillLoading: false, h2: "Error Displaying Data", and no unhandled page error. Splitting it into its own message was the right call — the two failure modes are now distinguishable from the UI alone.

✅ 3. Two typos in the new whatsNewText

  • Line 90: <li>Make timestamps human-readable. is missing its </li>. Renders fine today because the parser auto-closes at the next sibling, but it will silently swallow anything nested into that item later.
  • Line 77: "comas" → "commas".

Fixed in bc5d819.


That clears everything I raised. For the record, on bc5d819 I ran a full navigation pass — hdrs → assets → contracts → historical → treasury → status, then the Back button — in headless Chromium 151, Firefox and WebKit: zero page errors, zero console errors, URL tracked correctly throughout, and the file still parses with no duplicate top-level declarations across the script blocks.

Nothing further from me. Two notes on the new code, neither actionable: the ${page_id} interpolations in the "Error Displaying Data" markup are safe because the fallback at line 3488 constrains page_id to a pageConfig key, and the new try correctly needs no counter re-check of its own since it runs synchronously right after the one at line 3590. Nice refactor overall.

- Correct some typos.
- Avoid fetch error being displayed if the users has already moved to another page.
- Add a try/catch for possible errors in data parsing & display.

@Maxnflaxl Maxnflaxl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Maxnflaxl
Maxnflaxl merged commit 38ad729 into BeamMW:master Aug 20, 2026
1 of 2 checks passed
@dbadol
dbadol deleted the Explorer_HTM branch August 21, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants