Skip to content
Merged
Show file tree
Hide file tree
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.8.1 - 2026-09-12

### Fixed
- **Right clicking an entity with a page bound to it crashed the game on NeoForge.** The interaction event arrives on both the client and the server, and the client's copy was being handed to code that only a server can run: the first click on a bound entity took the whole client down. Fabric was never affected.
- **A page opened from an entity was told nothing about the entity.** `window.webgui.entity` was `null`, so one page bound to several NPCs had no way to tell which of them had been clicked. The entity was sent a moment before the screen that needed it, and opening that screen threw it away.
- **The HUD disappeared when one page replaced another.** Dying with a shop open — the death screen replaces the shop — or simply sending a second `/webgui gui` destroyed the HUD, and it stayed gone until the server sent it again.
- The pages a server hosts kept being served for a moment after the player left that server, on a token that leaving was supposed to revoke.

## 1.8.0 - 2026-09-10

### Added
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx4G
org.gradle.parallel=true
dev.kikugie.stonecutter.hard_mode=true

mod_version=1.8.0
mod_version=1.8.1
maven_group=land.webgui
archives_base_name=webgui

Expand Down
7 changes: 6 additions & 1 deletion src/main/java/land/webgui/EntityInteractionListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ public static void register() {

private static void onEntityInteract(PlayerInteractEvent.EntityInteract event) {
if (event.getHand() != InteractionHand.MAIN_HAND) return;

// The event fires on both sides, and on the client the player is a LocalPlayer.
// Casting it crashed the game on the first right click of a bound entity. Only the
// server can open a gui for a player anyway, which is what the Fabric side does.
if (!(event.getEntity() instanceof ServerPlayer sp)) return;

var opt = EntityBindingStore.get(event.getTarget().getUUID());
if (opt.isEmpty()) return;

ServerPlayer sp = (ServerPlayer) event.getEntity();
EntityBinding b = opt.get();
String url = WebviewPlaceholders.resolve(b.urlTemplate(), sp, event.getTarget());
String entityJson = WebviewEntityContext.buildJson(event.getTarget());
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/land/webgui/WebGUIAssetServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ public final class WebGUIAssetServer {

private static final SecureRandom RANDOM = new SecureRandom();

private static HttpServer server;
private static int port;
private static String sessionToken = "";
// Volatile because the eight threads serving requests read these without holding the
// lock the writers take: without it a request thread can keep honouring a token that
// leaving a server has already revoked.
private static volatile HttpServer server;
private static volatile int port;
private static volatile String sessionToken = "";
/** Paths already reported as missing, so one broken build logs each name once. */
private static final java.util.Set<String> WARNED = java.util.concurrent.ConcurrentHashMap.newKeySet();

Expand Down
7 changes: 6 additions & 1 deletion src/main/java/land/webgui/WebHudOverlay.java
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,12 @@ public static void onGuiOpened() {
//? } else {
/*public static void onGuiOpened() {*/
//? }
restoreHudAfterGuiClose = hudVisible && WebSession.mode() == WebSession.Mode.HUD_OVERLAY;
// Only decided when a gui is opening over the hud. A gui replacing another gui
// runs this too, and by then the mode is already GUI_SCREEN, so recomputing it
// here answered "there is no hud to put back" and threw the parked one away.
if (WebSession.mode() == WebSession.Mode.HUD_OVERLAY) {
restoreHudAfterGuiClose = hudVisible;
}
hudInteractive = false;
lastPixelW = -1;
lastPixelH = -1;
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/land/webgui/WebSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,17 @@ public static void closeGuiAndRestoreHud() {
}

public static RinkuBrowser openForGui(String url) {
closeSuspendedHudBrowser();
if (mode == Mode.HUD_OVERLAY && browser != null) {
// Coming from the hud: park it. Nothing should already be parked in this mode,
// but closing first keeps that from becoming a leak if it ever is.
closeSuspendedHudBrowser();
suspendedHudBrowser = browser;
browser = null;
} else {
// One gui replacing another, which is ordinary: dying with a gui open does it.
// Only the outgoing gui is closed here - whatever hud is parked belongs to
// whoever opened the first gui, and closing it here left the player with no
// hud at all once they escaped out.
closeActiveBrowser();
}
browser = Rinku.createBrowser(WebGUIAssetServer.resolve(url), true);
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/land/webgui/WebviewNetworking.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ public static void openHud(ServerPlayerEntity player, String url) {
}

public static void openGuiForEntity(ServerPlayerEntity player, String url, String entityJson) {
sendEntityContext(player, entityJson);
//? if >=1.20.5 {
ServerPlayNetworking.send(player, new WebviewPayloads.OpenWebS2CPayload(PROTOCOL_VERSION, MODE_GUI, withPlayerToken(player, url)));
//? } else {
Expand All @@ -215,6 +214,11 @@ public static void openGuiForEntity(ServerPlayerEntity player, String url, Strin
buf.writeString(withPlayerToken(player, url), MAX_URL_LENGTH);
ServerPlayNetworking.send(player, WebviewPayloads.OPEN_WEB_CHANNEL, buf);*/
//? }
// The context goes out after the open, not before it. Opening a gui builds a new
// browser and clears the client's push cache, and the entity context lives in that
// cache: sent first, it was wiped by the very screen it was meant for, and the page
// received null. Packets on one connection keep their order, so this arrives after.
sendEntityContext(player, entityJson);
}

public static void sendEntityContext(ServerPlayerEntity player, String entityJson) {
Expand Down Expand Up @@ -427,8 +431,12 @@ public static void openHud(ServerPlayer player, String url) {
}

public static void openGuiForEntity(ServerPlayer player, String url, String entityJson) {
sendEntityContext(player, entityJson);
PacketDistributor.sendToPlayer(player, new WebviewPayloads.OpenWebS2CPayload(PROTOCOL_VERSION, MODE_GUI, withPlayerToken(player, url)));
// The context goes out after the open, not before it. Opening a gui builds a new
// browser and clears the client's push cache, and the entity context lives in that
// cache: sent first, it was wiped by the very screen it was meant for, and the page
// received null. Packets on one connection keep their order, so this arrives after.
sendEntityContext(player, entityJson);
}

public static void sendEntityContext(ServerPlayer player, String entityJson) {
Expand Down
Loading