Rendering und Lua working, need to work on request method via custom URL implementation

This commit is contained in:
UnlegitDqrk
2026-02-10 23:13:58 +01:00
parent 8721c58e44
commit 278871d937
38 changed files with 757 additions and 1738 deletions

View File

@@ -105,11 +105,6 @@
</repositories>
<dependencies>
<dependency>
<groupId>org.openautonomousconnection</groupId>
<artifactId>Protocol</artifactId>
<version>1.0.0-BETA.7.7</version>
</dependency>
<dependency>
<groupId>org.openautonomousconnection</groupId>
<artifactId>OACSwing</artifactId>
@@ -124,12 +119,12 @@
<dependency>
<groupId>org.openautonomousconnection</groupId>
<artifactId>LuaScript</artifactId>
<version>1.0.0-BETA.1.4</version>
<version>1.0.0-BETA.1.5</version>
</dependency>
<dependency>
<groupId>org.openautonomousconnection</groupId>
<artifactId>InfoNameLib</artifactId>
<version>1.0.0-BETA.1.1</version>
<version>1.0.0-BETA.1.3</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>

View File

@@ -1,24 +1,13 @@
package org.openautonomousconnection.webclient.recode;
package org.openautonomousconnection.webclient;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketSendEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.infonamelib.OacWebUrlInstaller;
import org.openautonomousconnection.infonamelib.ProtocolHandlerPackages;
import org.openautonomousconnection.oacswing.component.OACOptionPane;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import javax.swing.*;
import java.util.List;
import java.util.Map;
public class ClientImpl extends ProtocolClient {
@Override
@@ -65,46 +54,17 @@ public class ClientImpl extends ProtocolClient {
return result == 0;
}
@Override
public void onResponse(INSResponseStatus status, List<INSRecord> records) {
try {
String host = records.getFirst().value;
if (!host.contains(":")) host = host + ":1028";
String[] split = host.split(":");
Main.getClient().getClientServerConnection().connect(split[0], Integer.parseInt(split[1]));
} catch (Exception e) {
Main.getClient().getProtocolBridge().getLogger().exception("Failed to connect to Server", e);
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to connect to Server:\n" + e.getMessage(),
"Server Connection", OACOptionPane.ERROR_MESSAGE);
}
super.onResponse(status, records);
}
@Listener
public void onConnected(ConnectedToProtocolINSServerEvent event) {
try {
Main.getClient().buildServerConnection(null, true);
buildServerConnection(null, getProtocolBridge().getProtocolValues().ssl);
ProtocolHandlerPackages.installPackage("org.openautonomousconnection.infonamelib");
OacWebUrlInstaller.installOnce(getProtocolBridge().getProtocolValues().eventManager, this);
SwingUtilities.invokeLater(() -> Main.getUi().openNewTab("web://info.oac/"));
} catch (Exception e) {
Main.getClient().getProtocolBridge().getLogger().exception("Failed to build Server connection", e);
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to connect to build Server connection:\n" + e.getMessage(),
"Server Connection", OACOptionPane.ERROR_MESSAGE);
}
Main.getUi().openNewTab("register.oac");
}
@Listener
public void onConnected(ConnectedToProtocolServerEvent event) {
try {
this.getClientServerConnection().sendPacket(new WebRequestPacket(
"index.html",
WebRequestMethod.GET,
Map.of(),
null
), TransportProtocol.TCP);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,100 +1,69 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import org.openautonomousconnection.infonamelib.InfoNames;
import lombok.Getter;
import org.openautonomousconnection.oacswing.component.design.Design;
import org.openautonomousconnection.oacswing.component.design.DesignManager;
import org.openautonomousconnection.oacswing.component.design.OACColor;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolValues;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.webclient.network.WebClient;
import org.openautonomousconnection.webclient.network.handlers.ServerPacketHandler;
import org.openautonomousconnection.webclient.packetlistener.listeners.WebPacketListener;
import org.openautonomousconnection.webclient.ui.MainFrame;
import org.openautonomousconnection.webclient.ui.dom.DOMContainerPanel;
import org.openautonomousconnection.webclient.settings.INSList;
import org.openautonomousconnection.webclient.ui.BrowserUI;
import org.openautonomousconnection.webclient.ui.FxBootstrap;
import java.beans.EventHandler;
import javax.swing.*;
import java.io.File;
public class Main {
public static MainFrame mainFrame;
public static final String DEFAULT_INS = "open-autonomous-connection.org";
@Getter
private static ClientImpl client;
public static final int DEFAULT_INS_PORT_TCP = 1026;
public static final int DEFAULT_WEB_PORT_TCP = 1028;
private static ProtocolBridge bridge;
private static final ProtocolVersion PROTOCOL_VERSION = ProtocolVersion.PV_1_0_0_BETA;
public static WebClient client;
public static ProtocolBridge bridge;
public static void main(String[] args) {
initProtocol();
InfoNames.registerOACInfoNameProtocols();
try {
registerEventHandlers();
} catch (Exception e) {
throw new RuntimeException(e);
}
registerPacketListeners();
/* Darkmode, wohoo! */
initDesigns();
DesignManager.setGlobalDesign(Design.DARK);
mainFrame = new MainFrame();
mainFrame.setVisible(true);
}
@Getter
private static BrowserUI ui;
private static void initProtocol() {
ProtocolValues values = new ProtocolValues();
values.packetHandler = new PacketHandler();
values.eventManager = new EventManager();
values.ssl = true;
client = new WebClient();
client = new ClientImpl();
try {
bridge = new ProtocolBridge(
client,
values,
PROTOCOL_VERSION,
ProtocolVersion.PV_1_0_0_BETA,
new File("logs")
);
);
// TODO
client.connectToINSServer(Main.DEFAULT_INS, -1);
client.buildINSConnection();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void registerEventHandlers() throws Exception {
EventManager eventManager = bridge.getProtocolValues().eventManager;
public static void main(String[] args) {
initProtocol();
FxBootstrap.ensureInitialized();
DesignManager.setGlobalDesign(Design.DARK);
eventManager.registerListener(new ServerPacketHandler());
}
SwingUtilities.invokeLater(() -> {
ui = new BrowserUI();
ui.setSize(1200, 800);
ui.setLocationRelativeTo(null);
ui.setVisible(true);
private static void registerPacketListeners() {
ServerPacketHandler.registerPacketListener(new WebPacketListener());
}
private static void initDesigns() {
//TODO
//DesignManager.getInstance().registerComponent(DOMContainerPanel.class, OACColor.DARK_BACKGROUND);
try {
bridge.getProtocolValues().eventManager.registerListener(client);
client.getClientINSConnection().connect(INSList.DEFAULT_INS, INSList.DEFAULT_PORT);
} catch (Exception exception) {
exception.printStackTrace(System.out);
}
});
}
}

View File

@@ -0,0 +1,36 @@
package org.openautonomousconnection.webclient.lua;
import org.openautonomousconnection.webclient.Main;
public class WebLogger {
private final String host;
public WebLogger(String host) {
this.host = host;
}
public void log(String string) {
Main.getClient().getProtocolBridge().getLogger().log(host + ": " + string);
}
public void info(String info) {
Main.getClient().getProtocolBridge().getLogger().info(host + ": " + info);
}
public void warn(String warn) {
Main.getClient().getProtocolBridge().getLogger().warn(host + ": " + warn);
}
public void error(String error) {
Main.getClient().getProtocolBridge().getLogger().error(host + ": " + error);
}
public void exception(String infoLine, Exception exception) {
Main.getClient().getProtocolBridge().getLogger().exception(host + ": " + infoLine, exception);
}
public void debug(String debug) {
Main.getClient().getProtocolBridge().getLogger().debug(host + ": " + debug);
}
}

View File

@@ -0,0 +1,37 @@
package org.openautonomousconnection.webclient.lua.hosts;
import org.openautonomousconnection.luascript.hosts.ConsoleHost;
import org.openautonomousconnection.webclient.lua.WebLogger;
public class ConsoleHostImpl implements ConsoleHost {
private final WebLogger logger;
public ConsoleHostImpl(WebLogger logger) {
this.logger = logger;
}
@Override
public void info(String message) {
logger.info(message);
}
@Override
public void log(String message) {
logger.log(message);
}
@Override
public void warn(String message) {
logger.warn(message);
}
@Override
public void error(String message) {
logger.error(message);
}
@Override
public void exception(String message) {
logger.exception("", new RuntimeException(message));
}
}

View File

@@ -0,0 +1,312 @@
package org.openautonomousconnection.webclient.lua.hosts;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import org.openautonomousconnection.luascript.fx.FxDomHost;
import org.openautonomousconnection.luascript.fx.FxThreadBridge;
import org.openautonomousconnection.luascript.hosts.UiHost;
import org.w3c.dom.Element;
import java.util.Objects;
/**
* UiHost implementation for JavaFX WebView (no JavaScript).
*
* <p>Operations are implemented via W3C DOM attributes/text, and best-effort behavior for
* value/style/class using standard attributes.</p>
*/
public final class UiHostImpl implements UiHost {
private final WebEngine engine;
private final WebView view;
private final FxDomHost dom;
/**
* Creates a new UI host.
*
* @param engine web engine
* @param view web view
* @param dom dom host
*/
public UiHostImpl(WebEngine engine, WebView view, FxDomHost dom) {
this.engine = Objects.requireNonNull(engine, "engine");
this.view = view;
this.dom = Objects.requireNonNull(dom, "dom");
}
@Override
public void alert(String message) {
// No JS: use simple JavaFX dialog-less fallback (log-style). You can replace with real Dialogs later.
// Keeping it deterministic and non-blocking for now.
System.out.println("[ui.alert] " + (message == null ? "" : message));
}
@Override
public boolean confirm(String message) {
// No JS: deterministic default (false). Replace with JavaFX dialogs if you want UI interaction.
System.out.println("[ui.confirm] " + (message == null ? "" : message));
return false;
}
@Override
public String prompt(String message, String defaultValue) {
// No JS: deterministic default.
System.out.println("[ui.prompt] " + (message == null ? "" : message));
return defaultValue;
}
@Override
public void setText(String elementId, String text) {
dom.setTextContent(elementId, text);
}
@Override
public String getText(String elementId) {
return dom.getTextContent(elementId);
}
@Override
public void setHtml(String elementId, String html) {
// Without JS, safest is to set textContent (prevents HTML parsing).
// If you need real HTML injection, we must extend DomHost with fragment parsing (not in current API).
dom.setTextContent(elementId, html);
}
@Override
public String getHtml(String elementId) {
// Without JS, best-effort: return textContent.
return dom.getTextContent(elementId);
}
@Override
public void setValue(String elementId, String value) {
// Input/textarea value is commonly reflected as attribute "value".
dom.setAttribute(elementId, "value", value == null ? "" : value);
}
@Override
public String getValue(String elementId) {
String v = dom.getAttribute(elementId, "value");
return v == null ? "" : v;
}
@Override
public void setEnabled(String elementId, boolean enabled) {
if (enabled) dom.removeAttribute(elementId, "disabled");
else dom.setAttribute(elementId, "disabled", "disabled");
}
@Override
public void setVisible(String elementId, boolean visible) {
// Best-effort via style attribute
String style = dom.getAttribute(elementId, "style");
style = style == null ? "" : style;
style = removeCssProp(style, "display");
if (!visible) {
style = style.trim();
if (!style.isEmpty() && !style.endsWith(";")) style += ";";
style += "display:none;";
}
dom.setAttribute(elementId, "style", style);
}
@Override
public void addClass(String elementId, String className) {
String cls = Objects.requireNonNull(className, "className").trim();
if (cls.isEmpty()) return;
FxThreadBridge.runAndWait(() -> {
Element el = dom.byId(elementId);
String c = el.getAttribute("class");
c = (c == null) ? "" : c.trim();
if (c.isEmpty()) {
el.setAttribute("class", cls);
return;
}
if (!hasClassToken(c, cls)) {
el.setAttribute("class", c + " " + cls);
}
});
}
@Override
public void removeClass(String elementId, String className) {
String cls = Objects.requireNonNull(className, "className").trim();
if (cls.isEmpty()) return;
FxThreadBridge.runAndWait(() -> {
Element el = dom.byId(elementId);
String c = el.getAttribute("class");
c = (c == null) ? "" : c.trim();
if (c.isEmpty()) return;
String[] parts = c.split("\\s+");
StringBuilder sb = new StringBuilder();
for (String p : parts) {
if (p.equals(cls)) continue;
if (!sb.isEmpty()) sb.append(' ');
sb.append(p);
}
el.setAttribute("class", sb.toString());
});
}
@Override
public boolean toggleClass(String elementId, String className) {
if (hasClass(elementId, className)) {
removeClass(elementId, className);
return false;
}
addClass(elementId, className);
return true;
}
@Override
public boolean hasClass(String elementId, String className) {
String cls = Objects.requireNonNull(className, "className").trim();
if (cls.isEmpty()) return false;
return FxThreadBridge.callAndWait(() -> {
Element el = dom.byId(elementId);
String c = el.getAttribute("class");
c = (c == null) ? "" : c.trim();
return !c.isEmpty() && hasClassToken(c, cls);
});
}
@Override
public void setStyle(String elementId, String property, String value) {
String prop = Objects.requireNonNull(property, "property").trim().toLowerCase();
if (prop.isEmpty()) return;
String style = dom.getAttribute(elementId, "style");
style = style == null ? "" : style;
style = removeCssProp(style, prop);
String v = value == null ? "" : value.trim();
if (!v.isEmpty()) {
style = style.trim();
if (!style.isEmpty() && !style.endsWith(";")) style += ";";
style += prop + ":" + v + ";";
}
dom.setAttribute(elementId, "style", style);
}
@Override
public String getStyle(String elementId, String property) {
// Best-effort parsing from style attribute.
String prop = Objects.requireNonNull(property, "property").trim().toLowerCase();
if (prop.isEmpty()) return "";
String style = dom.getAttribute(elementId, "style");
style = style == null ? "" : style;
for (String part : style.split(";")) {
String p = part.trim();
if (p.isEmpty()) continue;
int idx = p.indexOf(':');
if (idx <= 0) continue;
String k = p.substring(0, idx).trim().toLowerCase();
if (k.equals(prop)) return p.substring(idx + 1).trim();
}
return "";
}
@Override
public void setAttribute(String elementId, String name, String value) {
dom.setAttribute(elementId, name, value);
}
@Override
public String getAttribute(String elementId, String name) {
return dom.getAttribute(elementId, name);
}
@Override
public void removeAttribute(String elementId, String name) {
dom.removeAttribute(elementId, name);
}
@Override
public void focus(String elementId) {
engine.setJavaScriptEnabled(true);
engine.executeScript(
"document.getElementById('" + elementId + "').focus();"
);
engine.setJavaScriptEnabled(false);
}
@Override
public void blur(String elementId) {
engine.setJavaScriptEnabled(true);
engine.executeScript(
"document.getElementById('" + elementId + "').blur();"
);
engine.setJavaScriptEnabled(false);
}
@Override
public void scrollIntoView(String elementId) {
engine.setJavaScriptEnabled(true);
engine.executeScript(
"document.getElementById('" + elementId + "').scrollIntoView();"
);
engine.setJavaScriptEnabled(false);
}
@Override
public int viewportWidth() {
return FxThreadBridge.callAndWait(() ->
(int) Math.round(view.getWidth())
);
}
@Override
public int viewportHeight() {
return FxThreadBridge.callAndWait(() ->
(int) Math.round(view.getHeight())
);
}
@Override
public long nowMillis() {
return System.currentTimeMillis();
}
private static boolean hasClassToken(String classAttr, String cls) {
String[] parts = classAttr.trim().split("\\s+");
for (String p : parts) {
if (p.equals(cls)) return true;
}
return false;
}
private static String removeCssProp(String style, String propLower) {
if (style == null || style.isBlank()) return "";
StringBuilder sb = new StringBuilder();
for (String part : style.split(";")) {
String p = part.trim();
if (p.isEmpty()) continue;
int idx = p.indexOf(':');
if (idx <= 0) continue;
String k = p.substring(0, idx).trim().toLowerCase();
if (k.equals(propLower)) continue;
if (!sb.isEmpty()) sb.append(';');
sb.append(p);
}
String out = sb.toString().trim();
if (!out.isEmpty() && !out.endsWith(";")) out += ";";
return out;
}
}

View File

@@ -1,59 +0,0 @@
package org.openautonomousconnection.webclient.network;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.*;
/**
* An object that is still waited for to be delivered by the server
*
* @param <T> type of promised object
*/
public class Promise<T> {
@Getter @Setter
private T object;
public Promise() {
}
public Promise(T object) {
this.object = object;
}
public static <T> Promise<T> yieldPromise(int timeout, T reference) {
try (ExecutorService executorService = Executors.newSingleThreadExecutor()) {
Callable<T> task = () -> {
for(int i = 0; i < timeout; i++) {
if(reference == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
if(reference == null)
throw new TimeoutException();
return reference;
};
Future<T> future = executorService.submit(task);
executorService.shutdown();
try {
return new Promise<>(future.get());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
private Class<?> getType() {
return this.object.getClass();
}
}

View File

@@ -1,172 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.network;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import org.openautonomousconnection.webclient.network.type.NTFType;
import org.openautonomousconnection.webclient.network.type.NetTransmitFile;
import javax.swing.text.Document;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.openautonomousconnection.webclient.Main.*;
/**
* Uses the OAC-Protocol networking client
* Suppressing "unchecked" warnings because of generic casts
*/
@SuppressWarnings("unchecked")
public class WebClient extends ProtocolClient {
/**
* Global protocol timeout = 30 seconds
*/
public static final int TIMEOUT = 30;
/**
* Connect to INServer with URLs (ip based)
* @param ip ip to server
* @param tcpPort tcp ins port
*/
public void connectToINSServer(String ip, int tcpPort) throws NoSuchAlgorithmException, KeyManagementException {
tcpPort = tcpPort != -1 ? tcpPort : DEFAULT_INS_PORT_TCP;
// UDP is always preset
try {
this.buildINSConnection();
this.getClientINSConnection().connect(ip, tcpPort);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Give promise a non-null reference
* @param type type of file
* @param object serialized object
* @param <T> java object type
*/
public <T> void fulfillPromise(NTFType type, T object) {
if(!this.outstandingPromises.containsKey(type))
return;
Object promisedValue = this.outstandingPromises.get(type);
if(promisedValue != null)
return;
this.outstandingPromises.replace(type, object);
}
@Getter
private final Map<NTFType, Object> outstandingPromises = new HashMap<>();
/**
* Promise a serialized object that will be delivered as a file by the server
* @param type type of file
* @return object promise
* @param <T> java object type
*/
private <T> Promise<? extends NetTransmitFile<T>> promise(NTFType type) {
NetTransmitFile<T> ntf = NetTransmitFile.of(type, null);
this.outstandingPromises.put(type, ntf);
return Promise.yieldPromise(TIMEOUT, ntf);
}
@Override
public void onResponse(INSResponseStatus insResponseStatus, List<INSRecord> list) {
}
@Override
public boolean trustINS(String caFingerprint) {
//TODO
return true;
}
@Override
public boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint) {
//TODO
return true;
}
/**
* Send a get request for a file from the currently connected server
* @param type type of file requested (UNKNOWN for files not supported by the protocol)
* @param path path to file
* @param test idfk
* @return resolved file
* @param <T> java object type
*/
public <T> Promise<? extends NetTransmitFile<T>> get(NTFType type, String path, T test) {
try {
this.getClientServerConnection().sendPacket(new WebRequestPacket(
path,
WebRequestMethod.GET,
Map.of(),
null
), TransportProtocol.TCP);
} catch (Exception e) {
throw new RuntimeException(e);
}
return this.promise(type);
}
// TODO
public void post(String path) {
try {
this.getClientServerConnection().sendPacket(new WebRequestPacket(
path,
WebRequestMethod.POST,
Map.of(),
null
), TransportProtocol.TCP);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Get index.html Document of current site
* @return index.html
*/
public Promise<NetTransmitFile<Document>> getIndex() {
try {
this.getClientServerConnection().sendPacket(new WebRequestPacket(
"index.html",
WebRequestMethod.GET,
Map.of(),
null
), TransportProtocol.TCP);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println(promise(NTFType.DOCUMENT).getClass().getSimpleName());
return null;//(Promise<NetTransmitFile<Document>>) promise(NTFType.DOCUMENT);
}
}

View File

@@ -1,43 +0,0 @@
/* Author: Maple
* Jan. 16 2026
* */
package org.openautonomousconnection.webclient.network.handlers;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketSendEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.Packet;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.protocol.packets.OACPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
import org.openautonomousconnection.webclient.packetlistener.PacketListener;
import java.util.ArrayList;
import java.util.List;
public class ServerPacketHandler extends EventListener {
private static final List<PacketListener> listeners = new ArrayList<>();
public static void registerPacketListener(PacketListener packetListener) {
listeners.add(packetListener);
}
@Listener
public void onPacketReceived(C_PacketReadEvent packetReceivedEvent) {
Packet packet = packetReceivedEvent.getPacket();
TransportProtocol transport = packetReceivedEvent.getProtocol();
switch (packet.getPacketID()) {
case 4 -> listeners
.forEach(l -> l.onAuthPacketReceived((AuthPacket) packet, transport));
case 9 -> listeners
.forEach(l -> l.onWebResponsePacketReceived((WebResponsePacket) packet, transport));}
}
}

View File

@@ -1,13 +0,0 @@
/* Author: Maple
* Jan. 17 2026
* */
package org.openautonomousconnection.webclient.network.type;
public enum NTFType {
UNKNOWN,
STYLESHEET,
DOCUMENT,
ICON,
FILE
}

View File

@@ -1,34 +0,0 @@
/* Author: Maple
* Jan. 17 2026
* */
package org.openautonomousconnection.webclient.network.type;
import org.openautonomousconnection.webclient.network.type.application.NetAppstreamFile;
import org.openautonomousconnection.webclient.network.type.image.NetTransmitIcon;
import org.openautonomousconnection.webclient.network.type.text.NetTransmitDocument;
import org.openautonomousconnection.webclient.network.type.text.NetTransmitStylesheet;
import org.w3c.dom.Document;
import org.w3c.dom.css.CSSStyleSheet;
import javax.swing.*;
import java.io.File;
public interface NetTransmitFile<T> {
T getNtf();
void setNtf(T ntf);
@SuppressWarnings("unchecked")
static <T> NetTransmitFile<T> of(NTFType type, T ntf) {
return (NetTransmitFile<T>) switch (type) {
case FILE -> new NetAppstreamFile((File) ntf);
case ICON -> new NetTransmitIcon((ImageIcon) ntf);
case DOCUMENT -> new NetTransmitDocument((Document) ntf);
case STYLESHEET -> new NetTransmitStylesheet((CSSStyleSheet) ntf);
case null, default -> new NetTransmitUnknown(ntf);
};
}
}

View File

@@ -1,20 +0,0 @@
/* Author: Maple
* Jan. 17 2026
* */
package org.openautonomousconnection.webclient.network.type;
import lombok.Getter;
import lombok.Setter;
public class NetTransmitUnknown implements NetTransmitFile<Object> {
@Getter @Setter
private Object ntf;
public NetTransmitUnknown() {
}
public NetTransmitUnknown(Object ntf) {
this.ntf = ntf;
}
}

View File

@@ -1,25 +0,0 @@
/* Author: Maple
* Jan. 17 2026
* */
package org.openautonomousconnection.webclient.network.type.application;
import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.webclient.network.type.NetTransmitFile;
import java.io.File;
public class NetAppstreamFile implements NetTransmitFile<File> {
@Getter
@Setter
private File ntf;
public NetAppstreamFile() {
}
public NetAppstreamFile(File ntf) {
this.ntf = ntf;
}
}

View File

@@ -1,25 +0,0 @@
/* Author: Maple
* Jan. 17 2026
* */
package org.openautonomousconnection.webclient.network.type.image;
import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.webclient.network.type.NetTransmitFile;
import javax.swing.*;
public class NetTransmitIcon implements NetTransmitFile<ImageIcon> {
@Getter
@Setter
private ImageIcon ntf;
public NetTransmitIcon() {
}
public NetTransmitIcon(ImageIcon ntf) {
this.ntf = ntf;
}
}

View File

@@ -1,23 +0,0 @@
/* Author: Maple
* Jan. 17 2026
* */
package org.openautonomousconnection.webclient.network.type.text;
import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.webclient.network.type.NetTransmitFile;
import org.w3c.dom.Document;
public class NetTransmitDocument implements NetTransmitFile<Document> {
@Getter @Setter
private Document ntf;
public NetTransmitDocument() {
}
public NetTransmitDocument(Document ntf) {
this.ntf = ntf;
}
}

View File

@@ -1,25 +0,0 @@
/* Author: Maple
* Jan. 17 2026
* */
package org.openautonomousconnection.webclient.network.type.text;
import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.webclient.network.type.NetTransmitFile;
import org.w3c.dom.css.CSSStyleSheet;
public class NetTransmitStylesheet implements NetTransmitFile<CSSStyleSheet> {
@Getter
@Setter
private CSSStyleSheet ntf;
public NetTransmitStylesheet() {
}
public NetTransmitStylesheet(CSSStyleSheet ntf) {
this.ntf = ntf;
}
}

View File

@@ -1,104 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.network.website;
import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.webclient.Main;
import org.openautonomousconnection.webclient.network.Promise;
import org.openautonomousconnection.webclient.network.type.NTFType;
import org.openautonomousconnection.webclient.network.type.NetTransmitFile;
import org.w3c.dom.Document;
import javax.annotation.Nullable;
import javax.swing.*;
import java.net.URL;
import java.util.Objects;
/**
* Contains data about a site
* Suppressing "unchecked" warnings because of generic casts
*/
@SuppressWarnings("unchecked")
public final class WebSite {
@Getter
private final URL infoName;
@Getter @Setter
private Document dom;
/**
*
*/
public WebSite(URL infoName) {
this.infoName = infoName;
}
/**
* Send new get request
*/
public Document requestNewDom() {
Document test = null;
Promise<NetTransmitFile<Document>> promise = (Promise<NetTransmitFile<Document>>)
Main.client.get(NTFType.DOCUMENT, this.infoName.getPath().toString(), test);
return promise.getObject().getNtf();
}
/**
* Get favicon of site by attempting to retrieve the favicon.ico
*
* @return retrieved icon or null
*/
public Icon getFavIcon() {
return getFavIcon(this.infoName);
}
/**
* Get favicon of site by attempting to retrieve the favicon.ico
*
* @param infoName info name that leads to the site with the favicon
* @return retrieved icon or null
*/
public static @Nullable Icon getFavIcon(URL infoName) {
ImageIcon test = null;
Promise<NetTransmitFile<ImageIcon>> promise = (Promise<NetTransmitFile<ImageIcon>>)
Main.client.get(NTFType.ICON, infoName.getPath().toString(), test);
return promise.getObject().getNtf();
}
// /**
// * Get HTML text (dom) of site
// *
// * @param infoName that leads to the target OAC page
// * @return html document
// */
// public static Document getDom(InfoName infoName) {
// Main.client.getDocument();
// }
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (WebSite) obj;
return Objects.equals(this.infoName, that.infoName);
}
@Override
public int hashCode() {
return Objects.hash(infoName);
}
@Override
public String toString() {
return "WebSite{" +
"infoName=" + this.infoName +
", dom=" + this.dom +
'}';
}
}

View File

@@ -1,19 +0,0 @@
package org.openautonomousconnection.webclient.network.website.tab;
import org.openautonomousconnection.webclient.network.website.WebSite;
import javax.swing.*;
import java.net.URL;
public interface Tab {
Icon getFavicon();
void setFavicon(Icon favicon);
URL getInfoName();
void setInfoName(URL infoName);
WebSite getWebSite();
void setWebSite(WebSite webSite);
void refresh() throws Exception;
}

View File

@@ -1,91 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.network.website.tab;
import lombok.Getter;
import lombok.Setter;
import org.openautonomousconnection.webclient.network.WebClient;
import org.openautonomousconnection.webclient.network.website.WebSite;
import javax.swing.*;
import java.net.URL;
import java.util.Objects;
import static org.openautonomousconnection.webclient.Main.DEFAULT_WEB_PORT_TCP;
public final class WebTab implements Tab {
@Getter @Setter
private URL infoName;
@Getter @Setter
private Icon favicon;
@Getter @Setter
private WebSite webSite;
private WebClient client;
public WebTab(URL infoName, Icon favicon) throws Exception {
refresh();
this.infoName = infoName;
this.favicon = favicon;
this.webSite = new WebSite(infoName);
}
public WebTab(URL infoName) throws Exception {
refresh();
this.infoName = infoName;
this.favicon = WebSite.getFavIcon(infoName);
this.webSite = new WebSite(infoName);
}
public WebTab(WebSite webSite) throws Exception {
refresh();
this.infoName = webSite.getInfoName();
this.favicon = webSite.getFavIcon();
this.webSite = webSite;
}
public void refresh() throws Exception {
if (client.getClientServerConnection().isConnected()) client.getClientServerConnection().disconnect();
int port = infoName.getPort() == -1 ? DEFAULT_WEB_PORT_TCP : infoName.getPort();
client.buildServerConnection(null, true);
try {
client.getClientServerConnection().connect(infoName.getHost(), port);
} catch (Exception e) {
throw new RuntimeException(e);
}
this.webSite.requestNewDom();
this.favicon = this.webSite.getFavIcon();
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
WebTab webTab = (WebTab) o;
return Objects.equals(infoName, webTab.infoName) && Objects.equals(favicon, webTab.favicon) && Objects.equals(webSite, webTab.webSite) && Objects.equals(client, webTab.client);
}
@Override
public int hashCode() {
return Objects.hash(infoName, favicon, webSite, client);
}
@Override
public String toString() {
return "WebTab{" +
"infoName=" + infoName +
", favicon=" + favicon +
", webSite=" + webSite +
", client=" + client +
'}';
}
}

View File

@@ -1,27 +0,0 @@
/* Author: Maple
* Jan. 16 2026
* */
package org.openautonomousconnection.webclient.packetlistener;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.AuthPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import java.util.concurrent.TransferQueue;
public abstract class PacketListener {
public void onAuthPacketReceived(AuthPacket authPacket, TransportProtocol transport) {
}
public void onAuthPacketSent(AuthPacket authPacket, TransportProtocol transport) {
}
public void onWebResponsePacketReceived(WebResponsePacket webResponsePacket, TransportProtocol transport) {
}
public void onWebRequestPacketSent(WebRequestPacket webRequestPacket, TransportProtocol transport) {
}
}

View File

@@ -1,62 +0,0 @@
/* Author: Maple
* Jan. 16 2026
* */
package org.openautonomousconnection.webclient.packetlistener.listeners;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.webclient.Main;
import org.openautonomousconnection.webclient.network.type.NTFType;
import org.openautonomousconnection.webclient.network.type.NetTransmitFile;
import org.openautonomousconnection.webclient.network.type.image.NetTransmitIcon;
import org.openautonomousconnection.webclient.network.type.text.NetTransmitDocument;
import org.openautonomousconnection.webclient.packetlistener.PacketListener;
import org.openautonomousconnection.webclient.ui.dom.DomSerializer;
import org.w3c.dom.Document;
import javax.swing.*;
public class WebPacketListener extends PacketListener {
@Override
public void onWebResponsePacketReceived(WebResponsePacket packet, TransportProtocol transport) {
NTFType type = NTFType.UNKNOWN;
NetTransmitFile<?> file = null;
/*
full list of content types at https://repo.open-autonomous-connection.org/open-autonomous-connection/WebServer/src/branch/master/src/main/java/org/openautonomousconnection/webserver/ContentTypeResolver.java
*/
switch (packet.getContentType()) {
case "text/html":
type = NTFType.DOCUMENT;
// body is sent as byte array (string)
String htmlText = new String(packet.getBody());
Document dom = DomSerializer.fromString(Main.mainFrame.getDomContainerPanel().getWebEngine(), htmlText);
file = new NetTransmitDocument();
break;
case "image/png": // TODO: Implement
break;
case "image/ico": // the server doesn't implement this yet, so favicons won't work at this moment
type = NTFType.ICON;
ImageIcon icon = new ImageIcon(packet.getBody());
file = new NetTransmitIcon(icon);
break;
// the same as application/octet-stream
default:
break; //TODO: stream files
}
Main.client.fulfillPromise(type, file);
}
}

View File

@@ -1,72 +0,0 @@
package org.openautonomousconnection.webclient.recode;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import lombok.Getter;
import org.openautonomousconnection.infonamelib.InfoNames;
import org.openautonomousconnection.oacswing.component.OACOptionPane;
import org.openautonomousconnection.oacswing.component.design.Design;
import org.openautonomousconnection.oacswing.component.design.DesignManager;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolValues;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.webclient.recode.settings.INSList;
import org.openautonomousconnection.webclient.recode.ui.BrowserUI;
import javax.swing.*;
import java.io.File;
public class Main {
@Getter
private static ClientImpl client;
private static ProtocolBridge bridge;
@Getter
private static BrowserUI ui;
private static void initProtocol() {
InfoNames.registerOACInfoNameProtocols();
ProtocolValues values = new ProtocolValues();
values.packetHandler = new PacketHandler();
values.eventManager = new EventManager();
client = new ClientImpl();
try {
bridge = new ProtocolBridge(
client,
values,
ProtocolVersion.PV_1_0_0_BETA,
new File("logs")
);
client.buildINSConnection();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
initProtocol();
FxBootstrap.ensureInitialized();
DesignManager.setGlobalDesign(Design.DARK);
SwingUtilities.invokeLater(() -> {
ui = new BrowserUI();
ui.setSize(1200, 800);
ui.setLocationRelativeTo(null);
ui.setVisible(true);
try {
bridge.getProtocolValues().eventManager.registerListener(client);
client.getClientINSConnection().connect(INSList.DEFAULT_INS, INSList.DEFAULT_PORT);
} catch (Exception exception) {
client.getProtocolBridge().getLogger().exception("Failed to connect to INS", exception);
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to connect to INS Server:\n" + exception.getMessage(),
"INS Connection", OACOptionPane.ERROR_MESSAGE);
}
});
}
}

View File

@@ -1,197 +0,0 @@
package org.openautonomousconnection.webclient.recode.ui;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebHistory;
import javafx.scene.web.WebView;
import org.openautonomousconnection.infonamelib.InfoNames;
import org.openautonomousconnection.oacswing.component.OACOptionPane;
import org.openautonomousconnection.oacswing.component.OACPanel;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
import org.openautonomousconnection.webclient.recode.Main;
import javax.swing.*;
import java.awt.*;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* A Swing panel that embeds a JavaFX WebView.
* The JavaFX scene is initialized on first addNotify().
*/
public final class TabView extends OACPanel {
private final AtomicBoolean initialized = new AtomicBoolean(false);
private JFXPanel fxPanel;
private WebView webView;
private WebEngine engine;
private final Consumer<String> onLocationChanged;
/**
* Creates a new tab view.
*
* @param onLocationChanged callback invoked when the WebEngine location changes
*/
public TabView(Consumer<String> onLocationChanged) {
super();
this.onLocationChanged = Objects.requireNonNull(onLocationChanged, "onLocationChanged");
setLayout(new BorderLayout());
Main.getClient().getProtocolBridge().getProtocolValues().eventManager.registerListener(new TabListener(this));
}
@Override
public void addNotify() {
super.addNotify();
if (!initialized.compareAndSet(false, true)) {
return;
}
fxPanel = new JFXPanel();
add(fxPanel, BorderLayout.CENTER);
Platform.runLater(() -> {
webView = new WebView();
webView.setContextMenuEnabled(false);
webView.getEngine().setJavaScriptEnabled(false);
engine = webView.getEngine();
engine.locationProperty().addListener((obs, oldV, newV) -> {
if (newV != null) {
onLocationChanged.accept(newV);
}
});
fxPanel.setScene(new Scene(webView));
});
}
/**
* Loads a URL in this tab.
*
* @param url URL to load
*/
public void load(String url) {
String[] parts = url.split("\\.");
if (parts.length < 2 || parts.length > 3) {
throw new IllegalArgumentException(
"Invalid INS address format: " + url +
" (expected name.tln or sub.name.tln)"
);
}
String tln = parts[parts.length - 1];
String name = parts[parts.length - 2];
String sub = (parts.length == 3) ? parts[0] : null;
try {
Main.getClient().sendINSQuery(tln, name, sub, INSRecordType.A);
} catch (Exception e) {
Main.getClient().getProtocolBridge().getLogger().exception("Failed to send INS Query", e);
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to send INS Query:\n" + e.getMessage(),
"INS Connection", OACOptionPane.ERROR_MESSAGE);
return;
}
}
public static class TabListener extends EventListener {
private TabView view;
public TabListener(TabView view) {
this.view = view;
}
@Listener
public void onListen(C_PacketReadEvent event) {
if (event.getPacket() instanceof WebResponsePacket response) {
view.parseHtml(new String(response.getBody()));
}
}
}
public void parseHtml(String html) {
Platform.runLater(() -> {
if (engine != null) engine.loadContent(html);
});
}
/**
* Reloads the current page.
*/
public void reload() {
Platform.runLater(() -> {
if (engine != null) {
engine.reload();
}
});
}
/**
* Navigates one step back in history if possible.
*/
public void back() {
Platform.runLater(() -> {
if (engine == null) return;
WebHistory h = engine.getHistory();
if (h.getCurrentIndex() > 0) {
h.go(-1);
}
});
}
/**
* Navigates one step forward in history if possible.
*/
public void forward() {
Platform.runLater(() -> {
if (engine == null) return;
WebHistory h = engine.getHistory();
if (h.getCurrentIndex() < h.getEntries().size() - 1) {
h.go(1);
}
});
}
/**
* Returns the current location (best-effort, may be null until initialized).
*
* @return current location or null
*/
public String getEngineLocation() {
// No blocking: best-effort for UI sync.
return engine != null ? engine.getLocation() : null;
}
/**
* Disposes JavaFX scene references (best-effort).
*/
public void dispose() {
Platform.runLater(() -> {
if (engine != null) {
try {
engine.load(null);
} catch (Exception ignored) {
// Best-effort cleanup.
}
}
engine = null;
webView = null;
if (fxPanel != null) {
fxPanel.setScene(null);
}
});
}
}

View File

@@ -0,0 +1,144 @@
package org.openautonomousconnection.webclient.settings;
import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import org.luaj.vm2.Globals;
import org.openautonomousconnection.luascript.fx.FxDomHost;
import org.openautonomousconnection.luascript.fx.FxEventHost;
import org.openautonomousconnection.luascript.fx.FxWebViewResourceHost;
import org.openautonomousconnection.luascript.hosts.HostServices;
import org.openautonomousconnection.luascript.runtime.LuaRuntime;
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
import org.openautonomousconnection.webclient.lua.WebLogger;
import org.openautonomousconnection.webclient.lua.hosts.ConsoleHostImpl;
import org.openautonomousconnection.webclient.lua.hosts.UiHostImpl;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* JavaFX WebView integration entry point for LuaScript (no JavaScript).
*
* <p>Hard rule: every HTML script tag is treated as Lua.</p>
*/
public final class FxEngine implements AutoCloseable {
private final WebEngine engine;
private final WebView webView;
private final LuaExecutionPolicy policy;
private final WebLogger logger;
private final AtomicBoolean bootstrapped = new AtomicBoolean(false);
private LuaRuntime runtime;
/**
* Creates an integration engine with default UI execution policy.
*
* @param engine web engine
* @param webView web view
*/
public FxEngine(WebEngine engine, WebView webView, WebLogger logger) {
this(engine, webView, LuaExecutionPolicy.uiDefault(), logger);
}
/**
* Creates an integration engine with a custom execution policy.
*
* @param engine web engine
* @param webView web view
* @param policy execution policy
*/
public FxEngine(WebEngine engine, WebView webView, LuaExecutionPolicy policy, WebLogger logger) {
this.engine = Objects.requireNonNull(engine, "engine");
this.webView = webView;
this.policy = Objects.requireNonNull(policy, "policy");
this.logger = logger;
}
/**
* Installs a load hook that bootstraps Lua when a page finished loading.
*/
public void install() {
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
bootstrapped.set(false);
bootstrap();
} else if (newState == Worker.State.CANCELLED || newState == Worker.State.FAILED) {
bootstrapped.set(false);
closeRuntimeQuietly();
}
});
}
/**
* Bootstraps Lua for the currently loaded document.
*/
public void bootstrap() {
if (!bootstrapped.compareAndSet(false, true)) return;
closeRuntimeQuietly();
// DOM host must exist before event/UI tables, and must ensure stable ids.
FxDomHost dom = new FxDomHost(engine);
dom.ensureAllElementsHaveId();
// Create per-page globals; harden sandbox in production.
Globals globals = LuaGlobalsFactory.create(
new LuaGlobalsFactory.Options()
.enableDebug(false)
.sandbox(true)
);
// Create runtime first (router lives inside it).
ConsoleHostImpl console = new ConsoleHostImpl(logger);
UiHostImpl uiHost = new UiHostImpl(engine, webView, dom);
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
// runtime depends on services; events depends on runtime router.
// We'll create eventHost after runtime, then build HostServices with it.
LuaRuntime rt = new LuaRuntime(globals, new HostServices.Default(uiHost, dom, null, resourceHost, console), policy);
FxEventHost eventHost = new FxEventHost(dom, rt.eventRouter());
// Rebuild services including eventHost and reinstall tables.
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console);
// Replace runtime with correct services (clean and deterministic).
rt.close();
rt = new LuaRuntime(globals, services, policy);
rt.installStdTables(true);
rt.bootstrapFromDom();
this.runtime = rt;
}
/**
* Returns active runtime or null if not bootstrapped.
*
* @return runtime or null
*/
public LuaRuntime runtimeOrNull() {
return runtime;
}
@Override
public void close() {
closeRuntimeQuietly();
}
private void closeRuntimeQuietly() {
LuaRuntime rt = this.runtime;
this.runtime = null;
if (rt != null) {
try {
rt.close();
} catch (Exception ignored) {
// Best-effort shutdown.
}
}
}
}

View File

@@ -1,6 +1,4 @@
package org.openautonomousconnection.webclient.recode.settings;
import javafx.embed.swing.JFXPanel;
package org.openautonomousconnection.webclient.settings;
import java.util.HashMap;

View File

@@ -1,14 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.ui;
import org.openautonomousconnection.oacswing.component.OACFrame;
public abstract class BrowserFrame extends OACFrame {
protected BrowserFrame() {
}
}

View File

@@ -1,6 +1,4 @@
package org.openautonomousconnection.webclient.recode.ui;
import org.openautonomousconnection.webclient.recode.Main;
package org.openautonomousconnection.webclient.ui;
import java.util.Objects;
import java.util.function.Consumer;
@@ -22,7 +20,7 @@ public class BrowserTab {
*/
public BrowserTab(String initialUrl, Consumer<String> onLocationChange) {
this.key = Objects.requireNonNull(initialUrl, "initialUrl"); // placeholder key overwritten by BrowserUI
this.view = new TabView(onLocationChange);
this.view = new TabView(onLocationChange, initialUrl);
}
/**
@@ -41,8 +39,8 @@ public class BrowserTab {
*
* @param url URL
*/
public void load(String url) {
view.load(url);
public void loadUrl(String url) {
view.loadUrl(url);
}
/**
@@ -125,8 +123,8 @@ public class BrowserTab {
}
@Override
public void load(String url) {
fixedView.load(url);
public void loadUrl(String url) {
fixedView.loadUrl(url);
}
@Override

View File

@@ -1,4 +1,4 @@
package org.openautonomousconnection.webclient.recode.ui;
package org.openautonomousconnection.webclient.ui;
import org.openautonomousconnection.oacswing.component.OACButton;
import org.openautonomousconnection.oacswing.component.OACFrame;
@@ -109,7 +109,7 @@ public class BrowserUI extends OACFrame {
if (tab != null) tab.reload();
});
newTabButton.addActionListener(e -> openNewTab("info.oac"));
newTabButton.addActionListener(e -> openNewTab("web://info.oac/"));
closeTabButton.addActionListener(e -> closeCurrentTab());
// Create first tab
@@ -143,7 +143,7 @@ public class BrowserUI extends OACFrame {
getTitleBar().getTabs().setSelectedIndex(idx);
// Navigate
tab.load(url);
tab.loadUrl(url);
// Show content
cardLayout.show(pageHost, key);
@@ -165,7 +165,7 @@ public class BrowserUI extends OACFrame {
String url = normalizeUrl(input);
addressField.setText(url);
tab.load(url);
tab.loadUrl(url);
}
/**
@@ -187,7 +187,7 @@ public class BrowserUI extends OACFrame {
// If no tabs left, open a new one
if (getTitleBar().getTabs().getTabCount() == 0) {
openNewTab("info.oac");
openNewTab("web://info.oac/");
return;
}
@@ -225,8 +225,13 @@ public class BrowserUI extends OACFrame {
private static String normalizeUrl(String input) {
String s = input == null ? "" : input.trim();
if (s.isEmpty()) return "info.oac";
if (s.startsWith("web://")) return s;
return "web://" + s;
if (s.isEmpty()) return "web://info.oac/";
if (s.startsWith("web://")) {
// Ensure trailing slash for "host only" URLs
String rest = s.substring("web://".length());
if (!rest.contains("/")) return s + "/";
return s;
}
return "web://" + s + (s.contains("/") ? "" : "/");
}
}

View File

@@ -1,4 +1,4 @@
package org.openautonomousconnection.webclient.recode;
package org.openautonomousconnection.webclient.ui;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;

View File

@@ -1,70 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.ui;
import lombok.Getter;
import org.openautonomousconnection.webclient.network.website.tab.WebTab;
import org.openautonomousconnection.webclient.ui.dom.DOMContainerPanel;
import java.awt.*;
import java.net.URL;
public final class MainFrame extends BrowserFrame {
@Getter
private WebTab openWebTab;
@Getter
private final DOMContainerPanel domContainerPanel;
public MainFrame() {
super();
this.setSize(800, 600);
try {
// TODO this.openTab = new Tab(URI.create("web://127.0.0.1").toURL());
} catch (Exception e) {
throw new RuntimeException(e);
}
this.domContainerPanel = new DOMContainerPanel();
this.add(this.domContainerPanel, BorderLayout.CENTER);
}
private void init() {
this.open(this.openWebTab);
}
public void setOpenWebTab(int index) {
//TODO
}
public void setOpenWebTab(WebTab webTab) {
//TODO
// for(TabButton button : this.getTopBar().getTabButtons())
// if(button.getTab().equals(tab)) {
// this.openTab = button.getTab();
// button.grabFocus();
// }
}
public void open(URL url) throws Exception {
//TODO
// TabButton button = new TabButton(url);
//
// this.getTopBar().addButton(button);
//
// button.grabFocus();
}
public void open(WebTab webTab) {
//TODO
// TabButton button = new TabButton(tab);
//
// this.getTopBar().addButton(button);
//
// button.grabFocus();
}
}

View File

@@ -1,112 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.webclient.ui;
import lombok.Getter;
import org.openautonomousconnection.webclient.Main;
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class MenuButton extends JButton {
@Getter
private ClickAction clickAction;
public MenuButton(Icon icon, ClickAction clickAction) {
this.setIcon(icon);
this.clickAction = clickAction;
this.init();
}
public MenuButton(String text, ClickAction clickAction) {
this.setText(text);
this.clickAction = clickAction;
this.init();
}
private void init() {
this.setBackground(Color.gray);
this.setForeground(Color.lightGray);
this.setBorderPainted(false);
this.addActionListener(e -> {
switch (this.clickAction) {
case CLOSE -> Main.mainFrame.dispose();
case MINIMIZE -> Main.mainFrame.setState(JFrame.ICONIFIED);
case MAXIMIZE -> {
if(Main.mainFrame.getExtendedState() == JFrame.NORMAL) {
this.setText("(_)");
Main.mainFrame.setExtendedState(
Main.mainFrame.getExtendedState() | JFrame.MAXIMIZED_BOTH
);
}
else {
this.setText("[]");
Main.mainFrame.setExtendedState(JFrame.NORMAL);
}
}
}
});
this.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
setBorderPainted(false);
// setBackground(Color.getHSBColor(0.65f, 0.5f, 0.6f));
}
@Override
public void focusLost(FocusEvent e) {
// setBackground(Color.gray);
}
});
this.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
setBackground(Color.getHSBColor(0.65f, 0.3f, 0.5f));
}
@Override
public void mouseExited(MouseEvent e) {
setBackground(Color.gray);
}
});
}
public enum ClickAction {
CLOSE,
MINIMIZE,
MAXIMIZE
}
}

View File

@@ -1,44 +0,0 @@
/* Author: Maple
* Jan. 19 2026
* */
package org.openautonomousconnection.webclient.ui;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
public class RoundedBorder implements Border {
private final int radius;
public RoundedBorder(int radius) {
this.radius = radius;
}
@Override
public Insets getBorderInsets(Component c) {
return new Insets(radius, radius, radius, radius);
}
@Override
public boolean isBorderOpaque() {
return false;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(c.getForeground());
g2.draw(new RoundRectangle2D.Double(
x, y,
width - 1, height - 1,
radius, radius
));
g2.dispose();
}
}

View File

@@ -0,0 +1,165 @@
package org.openautonomousconnection.webclient.ui;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebHistory;
import javafx.scene.web.WebView;
import org.openautonomousconnection.oacswing.component.OACPanel;
import org.openautonomousconnection.webclient.lua.WebLogger;
import org.openautonomousconnection.webclient.settings.FxEngine;
import java.awt.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* A Swing panel that embeds a JavaFX WebView and runs LuaScript (no JavaScript).
*
* <p>Loads "web://" URLs so JavaFX can request subresources (CSS, images, href navigations)
* through URLConnection (mapped to OAC WebRequestPacket).</p>
*/
public final class TabView extends OACPanel {
private final AtomicBoolean initialized = new AtomicBoolean(false);
private JFXPanel fxPanel;
private WebView webView;
private WebEngine engine;
private final Consumer<String> onLocationChanged;
private final WebLogger webLogger;
private volatile FxEngine luaEngine;
/**
* Creates a new tab view.
*
* @param onLocationChanged callback invoked when the WebEngine location changes
* @param url callback invoked on URL changes
*/
public TabView(Consumer<String> onLocationChanged, String url) {
super();
this.onLocationChanged = Objects.requireNonNull(onLocationChanged, "onLocationChanged");
this.webLogger = new WebLogger(url);
setLayout(new BorderLayout());
}
@Override
public void addNotify() {
super.addNotify();
if (!initialized.compareAndSet(false, true)) {
return;
}
fxPanel = new JFXPanel();
add(fxPanel, BorderLayout.CENTER);
Platform.runLater(() -> {
webView = new WebView();
webView.setContextMenuEnabled(false);
engine = webView.getEngine();
engine.setJavaScriptEnabled(false);
engine.locationProperty().addListener((obs, oldV, newV) -> {
if (newV != null) {
onLocationChanged.accept(newV);
}
});
// Proper Lua integration from your library
luaEngine = new FxEngine(engine, webView, webLogger);
luaEngine.install();
fxPanel.setScene(new Scene(webView));
});
}
/**
* Loads a normalized URL (expected: web://...).
*
* @param url URL to load
*/
public void loadUrl(String url) {
String u = Objects.requireNonNull(url, "url").trim();
if (u.isEmpty()) return;
Platform.runLater(() -> {
if (engine != null) {
engine.load(u);
}
});
}
/**
* Reloads the current page.
*/
public void reload() {
Platform.runLater(() -> {
if (engine != null) engine.reload();
});
}
/**
* Navigates one step back in history if possible.
*/
public void back() {
Platform.runLater(() -> {
if (engine == null) return;
WebHistory h = engine.getHistory();
if (h.getCurrentIndex() > 0) h.go(-1);
});
}
/**
* Navigates one step forward in history if possible.
*/
public void forward() {
Platform.runLater(() -> {
if (engine == null) return;
WebHistory h = engine.getHistory();
if (h.getCurrentIndex() < h.getEntries().size() - 1) h.go(1);
});
}
/**
* Returns current engine location.
*
* @return location or null
*/
public String getEngineLocation() {
return engine != null ? engine.getLocation() : null;
}
/**
* Disposes resources.
*/
public void dispose() {
FxEngine le = luaEngine;
luaEngine = null;
if (le != null) {
try {
le.close();
} catch (Exception ignored) {
}
}
Platform.runLater(() -> {
if (engine != null) {
try {
engine.load(null);
} catch (Exception ignored) {
}
}
engine = null;
webView = null;
if (fxPanel != null) {
fxPanel.setScene(null);
}
});
}
}

View File

@@ -1,83 +0,0 @@
/* Author: Maple
* Jan. 18 2026
* */
package org.openautonomousconnection.webclient.ui;
import lombok.Getter;
import org.openautonomousconnection.webclient.Main;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
@Deprecated(forRemoval = true)
public class TopBar extends JPanel {
@Getter
private MenuButton closeButton, minimizeButton, maximizeButton;
@Getter
private JPanel buttonPanel;
private Point dragOffset;
public TopBar() {
this.setPreferredSize(new Dimension(0, 40));
this.buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
this.closeButton = new MenuButton("X", MenuButton.ClickAction.CLOSE);
this.minimizeButton = new MenuButton("-", MenuButton.ClickAction.MINIMIZE);
this.maximizeButton = new MenuButton("[]", MenuButton.ClickAction.MAXIMIZE);
this.buttonPanel.add(this.closeButton);
this.buttonPanel.add(this.minimizeButton);
this.buttonPanel.add(this.maximizeButton);
this.init();
}
private void init() {
this.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
Point mouseOnScreen = e.getLocationOnScreen();
Point frameLocation = Main.mainFrame.getLocation();
dragOffset = new Point(
mouseOnScreen.x - frameLocation.x,
mouseOnScreen.y - frameLocation.y
);
}
@Override
public void mouseReleased(MouseEvent e) {
dragOffset = null;
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (dragOffset != null) {
Point mouseOnScreen = e.getLocationOnScreen();
int newX = mouseOnScreen.x - dragOffset.x;
int newY = mouseOnScreen.y - dragOffset.y;
Main.mainFrame.setLocation(newX, newY);
}
}
});
}
public void initButtonPanel(String alignment) {
this.setLayout(new BorderLayout());
this.add(this.buttonPanel, alignment);
this.repaint();
}
}

View File

@@ -1,52 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.ui.dom;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import lombok.Getter;
import org.openautonomousconnection.oacswing.component.OACPanel;
import org.w3c.dom.Document;
import javax.swing.*;
import java.awt.*;
public class DOMContainerPanel extends OACPanel {
@Getter
private final JFXPanel dom;
@Getter
private WebView webView;
@Getter
private WebEngine webEngine;
public void loadContent(String html) {
this.webEngine.loadContent(html);
}
public void loadContent(Document html) {
this.loadContent(DomSerializer.toString(html));
}
public DOMContainerPanel() {
this.setBackground(Color.LIGHT_GRAY);
//TODO: Turn this into designable OAC-JFXpanel
this.dom = new JFXPanel();
Platform.runLater(() -> {
this.webView = new WebView();
this.webEngine = this.webView.getEngine();
this.webEngine.setJavaScriptEnabled(false);
this.dom.setScene(new Scene(this.webView));
});
this.add(this.dom);
}
}

View File

@@ -1,126 +0,0 @@
package org.openautonomousconnection.webclient.ui.dom;
import org.w3c.dom.Document;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import javafx.application.Platform;
import javafx.scene.web.WebEngine;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Serializes a W3C DOM Document to an HTML string.
*
* <p>Works with JavaFX WebEngine DOM.</p>
*/
public final class DomSerializer {
private DomSerializer() {
}
/**
* Converts a DOM {@link Document} to a string.
*
* @param document DOM document
* @return serialized HTML
*/
public static String toString(Document document) {
if (document == null) {
throw new IllegalArgumentException("document is null");
}
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.METHOD, "html");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
StringWriter writer = new StringWriter();
transformer.transform(
new DOMSource(document),
new StreamResult(writer)
);
return writer.toString();
} catch (Exception e) {
throw new RuntimeException("Failed to serialize DOM document", e);
}
}
/**
* Loads an HTML string into a JavaFX {@link WebEngine} and returns the resulting DOM {@link Document}.
*
* <p>No JavaScript required. Parsing is done by WebKit.</p>
*
* @param engine JavaFX WebEngine (must not be null)
* @param html HTML source
* @return parsed DOM document
*/
public static Document fromString(WebEngine engine, String html) {
Objects.requireNonNull(engine, "engine");
Objects.requireNonNull(html, "html");
if (!Platform.isFxApplicationThread()) {
final Document[] result = new Document[1];
final RuntimeException[] error = new RuntimeException[1];
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(() -> {
try {
result[0] = fromString(engine, html);
} catch (RuntimeException e) {
error[0] = e;
} finally {
latch.countDown();
}
});
try {
if (!latch.await(5, TimeUnit.SECONDS)) {
throw new RuntimeException("Timed out while parsing HTML");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while parsing HTML", e);
}
if (error[0] != null) throw error[0];
return result[0];
}
CountDownLatch latch = new CountDownLatch(1);
engine.getLoadWorker().stateProperty().addListener((obs, o, n) -> {
switch (n) {
case SUCCEEDED, FAILED, CANCELLED -> latch.countDown();
}
});
engine.loadContent(html, "text/html");
try {
if (!latch.await(5, TimeUnit.SECONDS)) {
throw new RuntimeException("Timed out while parsing HTML");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while parsing HTML", e);
}
Document doc = engine.getDocument();
if (doc == null) {
throw new IllegalStateException("WebEngine did not produce a DOM document");
}
return doc;
}
}

View File

@@ -1,35 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.ui.tab;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.openautonomousconnection.oacswing.component.OACButton;
import org.openautonomousconnection.webclient.network.website.tab.WebTab;
import javax.swing.*;
import java.net.URI;
import java.net.URL;
/**
* Button that contains Tab data
*/
@Getter @Setter
public class TabButton extends OACButton {
private WebTab webTab;
public TabButton(@NonNull String infoName) throws Exception {
this(URI.create(infoName).toURL());
}
public TabButton(@NonNull URL infoName) throws Exception {
this.webTab = new WebTab(infoName);
}
public TabButton(@NonNull WebTab webTab) {
this.webTab = webTab;
}
}

View File

@@ -1,53 +0,0 @@
/* Author: Maple
* Dec. 12 2025
* */
package org.openautonomousconnection.webclient.ui.tab;
import lombok.Getter;
import org.openautonomousconnection.oacswing.component.OACTitleBar;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* View at the top of the screen that contains the tabs
* @deprecated the OAC-Swing library's solution fits better and works globally
*/
@Deprecated(forRemoval = true, since = "1.0.0-BETA.1.4")
public class TabButtonView extends OACTitleBar {
@Getter
private final List<TabButton> tabButtons;
public TabButtonView() {
this(new ArrayList<>());
}
/**
* Constructor with preset buttons
* @param tabButtons already created buttons
*/
public TabButtonView(Collection<TabButton> tabButtons) {
super(null);
this.tabButtons = (List<TabButton>) tabButtons;
FlowLayout layoutStyle = new FlowLayout(FlowLayout.RIGHT, 0, 0);
this.setLayout(layoutStyle);
this.setBackground(Color.gray);
}
public void addButton(TabButton tabButton) {
this.tabButtons.add(tabButton);
}
public TabButton getButton(int index) {
return this.tabButtons.get(index);
}
}