diff --git a/client/pom.xml b/client/pom.xml
index d48d069..3bd793a 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -34,21 +34,25 @@
jersey-client
${version.jersey}
+
org.glassfish.jersey.inject
jersey-hk2
${version.jersey}
+
org.glassfish.jersey.media
jersey-media-json-jackson
${version.jersey}
+
jakarta.activation
jakarta.activation-api
2.1.3
+
com.google.inject
guice
@@ -61,11 +65,13 @@
javafx-fxml
${version.jfx}
+
org.openjfx
javafx-controls
${version.jfx}
+
org.openjfx
javafx-web
@@ -91,6 +97,27 @@
${version.mockito}
test
+
+
+
+ org.springframework
+ spring-messaging
+ 6.2.12
+ compile
+
+
+
+ org.glassfish.tyrus.bundles
+ tyrus-standalone-client
+ 2.2.1
+
+
+ org.springframework
+ spring-websocket
+ 6.2.12
+ compile
+
+
diff --git a/client/src/main/java/client/MyModule.java b/client/src/main/java/client/MyModule.java
index 4f3464e..21da1dc 100644
--- a/client/src/main/java/client/MyModule.java
+++ b/client/src/main/java/client/MyModule.java
@@ -19,6 +19,8 @@ import client.scenes.FoodpalApplicationCtrl;
import client.scenes.recipe.IngredientListCtrl;
import client.scenes.recipe.RecipeStepListCtrl;
import client.utils.LocaleManager;
+import client.utils.ServerUtils;
+import client.utils.WebSocketUtils;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Scopes;
@@ -33,6 +35,9 @@ public class MyModule implements Module {
binder.bind(FoodpalApplicationCtrl.class).in(Scopes.SINGLETON);
binder.bind(IngredientListCtrl.class).in(Scopes.SINGLETON);
binder.bind(RecipeStepListCtrl.class).in(Scopes.SINGLETON);
+
binder.bind(LocaleManager.class).in(Scopes.SINGLETON);
+ binder.bind(ServerUtils.class).in(Scopes.SINGLETON);
+ binder.bind(WebSocketUtils.class).in(Scopes.SINGLETON);
}
}
\ No newline at end of file
diff --git a/client/src/main/java/client/utils/WebSocketUtils.java b/client/src/main/java/client/utils/WebSocketUtils.java
new file mode 100644
index 0000000..ed16b37
--- /dev/null
+++ b/client/src/main/java/client/utils/WebSocketUtils.java
@@ -0,0 +1,111 @@
+package client.utils;
+
+import commons.ws.messages.Message;
+import org.springframework.messaging.converter.MappingJackson2MessageConverter;
+import org.springframework.messaging.simp.stomp.StompSession;
+import org.springframework.messaging.simp.stomp.StompHeaders;
+import org.springframework.messaging.simp.stomp.StompCommand;
+import org.springframework.messaging.simp.stomp.StompFrameHandler;
+import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
+import org.springframework.web.socket.client.standard.StandardWebSocketClient;
+import org.springframework.web.socket.messaging.WebSocketStompClient;
+
+import java.lang.reflect.Type;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import java.util.concurrent.CompletableFuture;
+
+public class WebSocketUtils {
+ private static final String WS_URL = "ws://localhost:8080/updates";
+ private WebSocketStompClient stompClient;
+ private StompSession stompSession;
+
+ /**
+ * Connect to the websocket server.
+ * @param onConnected OnConnected callback
+ */
+ public void connect(@Nullable Runnable onConnected) {
+ StandardWebSocketClient webSocketClient = new StandardWebSocketClient(); // Create WS Client
+
+ stompClient = new WebSocketStompClient(webSocketClient);
+ stompClient.setMessageConverter(new MappingJackson2MessageConverter()); // Use jackson
+
+ CompletableFuture sessionFuture = stompClient.connectAsync(
+ WS_URL,
+ new StompSessionHandlerAdapter() {
+ @Override
+ public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
+ stompSession = session;
+ System.out.println("WebSocket connected: " + session.getSessionId());
+ }
+
+ @Override
+ public void handleException(StompSession session, @Nullable StompCommand command,
+ StompHeaders headers, byte[] payload,
+ Throwable exception) {
+ System.err.println("STOMP error: " + exception.getMessage());
+ exception.printStackTrace();
+ }
+
+ @Override
+ public void handleTransportError(StompSession session, Throwable exception) {
+ System.err.println("STOMP transport error: " + exception.getMessage());
+ exception.printStackTrace();
+ }
+ }
+ );
+
+ sessionFuture.thenAccept(session -> {
+ stompSession = session;
+ System.out.println("Connection successful, session ready");
+ if (onConnected != null) {
+ onConnected.run();
+ }
+ }).exceptionally(throwable -> {
+ System.err.println("Failed to connect: " + throwable.getMessage());
+ throwable.printStackTrace();
+ return null;
+ });
+ }
+
+ /**
+ * Subscribe to a topic.
+ * @param destination Destination to subscribe to, for example: /subscribe/recipe
+ * @param messageHandler Handler for received messages
+ */
+ public void subscribe(String destination, Consumer messageHandler) {
+ if (stompSession == null || !stompSession.isConnected()) {
+ System.err.println("Cannot subscribe - not connected");
+ return;
+ }
+
+ stompSession.subscribe(destination, new StompFrameHandler() {
+ @Override
+ public Type getPayloadType(StompHeaders headers) {
+ return Message.class;
+ }
+
+ @Override
+ public void handleFrame(StompHeaders headers, @Nullable Object payload) {
+ Message message = (Message) payload;
+ messageHandler.accept(message);
+ }
+ });
+ System.out.println("Subscribed to: " + destination);
+ }
+
+ public void disconnect() {
+ if (stompSession != null && stompSession.isConnected()) {
+ stompSession.disconnect();
+ }
+
+ if (stompClient != null) {
+ stompClient.stop();
+ }
+ }
+
+ public boolean isConnected() {
+ return stompSession != null && stompSession.isConnected();
+ }
+
+}
diff --git a/commons/pom.xml b/commons/pom.xml
index e5d5ff4..f48fbcf 100644
--- a/commons/pom.xml
+++ b/commons/pom.xml
@@ -45,6 +45,12 @@
${version.mockito}
test
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.20.1
+ compile
+