Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function isLocalURL(url) {
export function bootstrap() {
console.assert(
!!window,
"boostrap error: must be called from within a browser context",
"bootstrap error: must be called from within a browser context",
);
const link = window.document.querySelector(
'head link[rel*="alternate"][type="application/vnd.api+json"]',
Expand All @@ -93,7 +93,53 @@ export function bootstrap() {
)
: new URL(link.getAttribute("href"));
const client = new Client(initialURL.href);
// Register a global listener for history updates.

// Add click event listener to div#app
const appDiv = document.getElementById("app");
console.assert(!!appDiv, "bootstrap error: missing div#app element");
appDiv.addEventListener("click", (event) => {
// Check if the target is an anchor element
if (!(event.target instanceof HTMLAnchorElement)) return;

const anchor = event.target;
Comment on lines +102 to +104
Copy link
Contributor

Choose a reason for hiding this comment

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

This is too strict, for example the markup could be something like <a href=""><div class="title">Click me</div></a> Then the event.target would be the div.title instead of the anchor and this will return.

Suggested change
if (!(event.target instanceof HTMLAnchorElement)) return;
const anchor = event.target;
const anchor = event.target instanceof HTMLAnchorElement ? event.target : event.target.closest('a');
if (!anchor) return;

const href = anchor.href; // Safe to access href directly
Copy link
Contributor

@zrpnr zrpnr Aug 12, 2025

Choose a reason for hiding this comment

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

the href property seems to always be absolute, let's use

Suggested change
const href = anchor.href; // Safe to access href directly
const href = anchor.getAttribute("href");


// Check if href is external or has a non-web protocol
let url;
try {
url = new URL(href, appDiv.baseURI);
} catch {
console.warn(`Invalid href: ${href}`);
return;
}
const isExternal = url.protocol !== "http:" && url.protocol !== "https:" ||
url.origin !== new URL(appDiv.baseURI).origin;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not critical to change now but this could just be isKnownProtocol since we also do the isExternal check by comparing the origins inside follow

if (isExternal) return;

// Check if anchor has a type attribute (opt-out)
if (anchor.hasAttribute("type")) return;

// Check if anchor has a download attribute
if (anchor.hasAttribute("download")) return;

// Check if anchor has a target attribute that is not _blank
if (
anchor.hasAttribute("target") &&
anchor.getAttribute("target") !== "_blank"
Copy link
Member

Choose a reason for hiding this comment

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

👍

) return;

// Prevent default behavior and stop propagation
event.preventDefault();
event.stopPropagation();

// Use normalized href
const normalizedHREF = url.href;

// Call the client's follow function
client.follow(normalizedHREF);
Copy link
Contributor

Choose a reason for hiding this comment

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

here normalizedHREF is now the app baseuri and not the api baseURI so follow will return early.
I think it's safe to just use the href here since we have already checked for the protocol and isExternal

Suggested change
client.follow(normalizedHREF);
client.follow(href);

});

// Register a global listener for history updates
addEventListener("popstate", (event) => {
// Not navigating without a state.
if (event.state) {
Expand All @@ -106,6 +152,7 @@ export function bootstrap() {
}
}
});

return client;
}

Expand Down