Skip to content

Commit 7679e85

Browse files
committed
New message notification
1 parent 000eee1 commit 7679e85

File tree

14 files changed

+214
-6
lines changed

14 files changed

+214
-6
lines changed

crypto-messenger-coder/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>org.example</groupId>
99
<artifactId>crypto-messenger</artifactId>
10-
<version>1.0.0</version>
10+
<version>1.1.0</version>
1111
</parent>
1212

1313
<artifactId>crypto-messenger-coder</artifactId>

crypto-messenger-desktop/pom.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>org.example</groupId>
99
<artifactId>crypto-messenger</artifactId>
10-
<version>1.0.0</version>
10+
<version>1.1.0</version>
1111
</parent>
1212

1313
<artifactId>crypto-messenger-desktop</artifactId>
@@ -28,6 +28,11 @@
2828
<artifactId>spring-boot-starter-web</artifactId>
2929
</dependency>
3030

31+
<dependency>
32+
<groupId>org.springframework.boot</groupId>
33+
<artifactId>spring-boot-starter-websocket</artifactId>
34+
</dependency>
35+
3136
<dependency>
3237
<groupId>org.springframework.cloud</groupId>
3338
<artifactId>spring-cloud-starter-openfeign</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package cryptomessenger.desktop.infrastructure.client.websocket;
2+
3+
import cryptomessenger.desktop.infrastructure.client.websocket.handler.MessageHandler;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.boot.context.event.ApplicationReadyEvent;
8+
import org.springframework.context.event.EventListener;
9+
import org.springframework.messaging.converter.StringMessageConverter;
10+
import org.springframework.messaging.simp.stomp.StompFrameHandler;
11+
import org.springframework.messaging.simp.stomp.StompHeaders;
12+
import org.springframework.messaging.simp.stomp.StompSession;
13+
import org.springframework.messaging.simp.stomp.StompSession.Subscription;
14+
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
15+
import org.springframework.stereotype.Component;
16+
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
17+
import org.springframework.web.socket.messaging.WebSocketStompClient;
18+
19+
import java.lang.reflect.Type;
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.Set;
22+
import java.util.function.Consumer;
23+
24+
import static java.util.Collections.emptySet;
25+
import static java.util.stream.Collectors.toSet;
26+
27+
@Slf4j
28+
@Component
29+
@RequiredArgsConstructor
30+
public class WebSocketManager {
31+
32+
private final Set<MessageHandler> messageHandlers;
33+
34+
@Value("${app.server-base-url}")
35+
private String serverBaseUrl;
36+
37+
private StompSession session;
38+
private Set<Subscription> subscriptions = emptySet();
39+
40+
@EventListener(ApplicationReadyEvent.class)
41+
private void connect() {
42+
var client = new WebSocketStompClient(new StandardWebSocketClient());
43+
client.setMessageConverter(new StringMessageConverter(StandardCharsets.UTF_8));
44+
client.connectAsync(getConnectionUrl(), new SessionHandler())
45+
.thenAccept(establishedSession -> {
46+
log.info("Connection established");
47+
session = establishedSession;
48+
refreshSubscriptions();
49+
});
50+
}
51+
52+
private String getConnectionUrl() {
53+
return serverBaseUrl.replaceFirst("http", "ws") + "/ws";
54+
}
55+
56+
public void refreshSubscriptions() {
57+
subscriptions.forEach(Subscription::unsubscribe);
58+
subscriptions = messageHandlers.stream().map(handler -> {
59+
var destination = handler.getDestination();
60+
var subscription = session.subscribe(destination, new FrameHandler(handler.getHandler()));
61+
log.info("Subscribed to {}", destination);
62+
return subscription;
63+
}).collect(toSet());
64+
}
65+
66+
private static class SessionHandler extends StompSessionHandlerAdapter {
67+
@Override
68+
public void handleTransportError(StompSession session, Throwable exception) {
69+
log.warn("Transport error: {}", exception.getMessage());
70+
}
71+
}
72+
73+
@RequiredArgsConstructor
74+
private static class FrameHandler implements StompFrameHandler {
75+
76+
private final Consumer<String> consumer;
77+
78+
@Override
79+
public Type getPayloadType(StompHeaders headers) {
80+
return String.class;
81+
}
82+
83+
@Override
84+
public void handleFrame(StompHeaders headers, Object payload) {
85+
consumer.accept((String) payload);
86+
}
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package cryptomessenger.desktop.infrastructure.client.websocket.handler;
2+
3+
import java.util.function.Consumer;
4+
5+
public interface MessageHandler {
6+
7+
String getDestination();
8+
9+
Consumer<String> getHandler();
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cryptomessenger.desktop.infrastructure.client.websocket.handler;
2+
3+
import cryptomessenger.desktop.infrastructure.client.user.UserClient;
4+
import cryptomessenger.desktop.infrastructure.localstorage.LocalStorage;
5+
import cryptomessenger.desktop.service.LocalStorageKeys;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.Setter;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.util.function.Consumer;
11+
12+
@Component
13+
@RequiredArgsConstructor
14+
public class NewMessageHandler implements MessageHandler {
15+
16+
private final LocalStorage localStorage;
17+
private final UserClient userClient;
18+
19+
@Setter
20+
private Runnable onNewMessage;
21+
22+
@Override
23+
public String getDestination() {
24+
var username = localStorage.getString(LocalStorageKeys.CURRENT_USERNAME);
25+
var user = userClient.getByUsername(username);
26+
return "/user/%s/messages/new".formatted(user.getId());
27+
}
28+
29+
@Override
30+
public Consumer<String> getHandler() {
31+
return message -> {
32+
if (onNewMessage != null) {
33+
onNewMessage.run();
34+
}
35+
};
36+
}
37+
}

crypto-messenger-desktop/src/main/java/cryptomessenger/desktop/infrastructure/ui/JavaFxApplication.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@ public class JavaFxApplication extends Application {
1212
private static ApplicationContext applicationContext;
1313

1414
private final SceneLoader sceneLoader;
15+
private final String appVersion;
1516

1617
public JavaFxApplication() {
1718
if (applicationContext == null) {
1819
throw new RuntimeException("Application context not provided");
1920
}
2021
sceneLoader = applicationContext.getBean(SceneLoader.class);
22+
appVersion = applicationContext.getEnvironment().getProperty("app.version");
2123
}
2224

2325
@Override
2426
public void start(Stage primaryStage) {
25-
primaryStage.setTitle("CryptoMessenger");
27+
primaryStage.setTitle("CryptoMessenger — %s".formatted(appVersion));
2628
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/graphics/lock.png")));
2729
primaryStage.setScene(sceneLoader.load("MainScene"));
2830
primaryStage.setMinWidth(700);

crypto-messenger-desktop/src/main/java/cryptomessenger/desktop/infrastructure/ui/controller/MainSceneController.java

+14
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package cryptomessenger.desktop.infrastructure.ui.controller;
22

3+
import cryptomessenger.desktop.infrastructure.client.websocket.WebSocketManager;
4+
import cryptomessenger.desktop.infrastructure.client.websocket.handler.NewMessageHandler;
35
import cryptomessenger.desktop.infrastructure.ui.Refreshable;
46
import cryptomessenger.desktop.infrastructure.ui.dialog.SendMessageDialog;
57
import cryptomessenger.desktop.service.message.Message;
68
import cryptomessenger.desktop.service.message.MessageService;
79
import cryptomessenger.desktop.service.user.UserService;
10+
import cryptomessenger.desktop.utility.Player;
811
import cryptomessenger.desktop.utility.ThreadFactories;
912
import javafx.collections.FXCollections;
1013
import javafx.event.ActionEvent;
@@ -19,6 +22,8 @@
1922
import org.springframework.data.domain.Pageable;
2023
import org.springframework.stereotype.Component;
2124

25+
import java.net.URL;
26+
import java.util.ResourceBundle;
2227
import java.util.concurrent.Executors;
2328
import java.util.concurrent.ScheduledExecutorService;
2429
import java.util.concurrent.TimeUnit;
@@ -31,6 +36,8 @@ public class MainSceneController implements Refreshable {
3136
private final UserService userService;
3237
private final MessageService messageService;
3338
private final SendMessageDialog sendMessageDialog;
39+
private final NewMessageHandler newMessageHandler;
40+
private final WebSocketManager webSocketManager;
3441

3542
public TextField usernameField;
3643
public Button registerButton;
@@ -41,6 +48,12 @@ public class MainSceneController implements Refreshable {
4148

4249
private ScheduledExecutorService executor;
4350

51+
@Override
52+
public void initialize(URL location, ResourceBundle resources) {
53+
refresh();
54+
newMessageHandler.setOnNewMessage(() -> Player.playAudio("/sounds/new-message.mp3"));
55+
}
56+
4457
@Override
4558
public void refresh() {
4659
var username = userService.getCurrentUsername();
@@ -50,6 +63,7 @@ public void refresh() {
5063
refreshInboxTable();
5164
refreshOutboxTable();
5265
configureAutoRefresh();
66+
webSocketManager.refreshSubscriptions();
5367
}
5468

5569
private void refreshInboxTable() {

crypto-messenger-desktop/src/main/resources/application.yml

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
app:
2+
version: @project.version@
23
server-base-url: http://164.90.184.120
34
spring:
45
main:

crypto-messenger-server/pom.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>org.example</groupId>
99
<artifactId>crypto-messenger</artifactId>
10-
<version>1.0.0</version>
10+
<version>1.1.0</version>
1111
</parent>
1212

1313
<artifactId>crypto-messenger-server</artifactId>
@@ -18,6 +18,11 @@
1818
<artifactId>spring-boot-starter-web</artifactId>
1919
</dependency>
2020

21+
<dependency>
22+
<groupId>org.springframework.boot</groupId>
23+
<artifactId>spring-boot-starter-websocket</artifactId>
24+
</dependency>
25+
2126
<dependency>
2227
<groupId>org.springframework.boot</groupId>
2328
<artifactId>spring-boot-starter-data-mongodb</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cryptomessenger.server.infrastructure.websocket;
2+
3+
import cryptomessenger.server.service.message.Message;
4+
import cryptomessenger.server.service.message.NewMessageListener;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.messaging.simp.SimpMessagingTemplate;
7+
import org.springframework.stereotype.Component;
8+
9+
@Component
10+
@RequiredArgsConstructor
11+
public class NewMessageNotifier implements NewMessageListener {
12+
13+
private final SimpMessagingTemplate simpMessagingTemplate;
14+
15+
@Override
16+
public void onNewMessage(Message message) {
17+
var receiver = message.getReceiverId().toString();
18+
simpMessagingTemplate.convertAndSendToUser(receiver, "/messages/new", "New message");
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cryptomessenger.server.infrastructure.websocket;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
5+
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
6+
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
7+
8+
@Configuration
9+
@EnableWebSocketMessageBroker
10+
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
11+
12+
@Override
13+
public void registerStompEndpoints(StompEndpointRegistry registry) {
14+
registry.addEndpoint("/ws");
15+
}
16+
}

crypto-messenger-server/src/main/java/cryptomessenger/server/service/message/MessageServiceImpl.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.springframework.stereotype.Service;
77

88
import java.time.LocalDateTime;
9+
import java.util.Set;
910
import java.util.UUID;
1011

1112
import static java.util.UUID.randomUUID;
@@ -15,6 +16,7 @@
1516
public class MessageServiceImpl implements MessageService {
1617

1718
private final MessageRepository messageRepository;
19+
private final Set<NewMessageListener> newMessageListeners;
1820

1921
@Override
2022
public Message send(MessageSending sending) {
@@ -26,7 +28,9 @@ public Message send(MessageSending sending) {
2628
.contentForSender(sending.getContentForSender())
2729
.contentForReceiver(sending.getContentForReceiver())
2830
.build();
29-
return messageRepository.save(message);
31+
messageRepository.save(message);
32+
newMessageListeners.forEach(listener -> listener.onNewMessage(message));
33+
return message;
3034
}
3135

3236
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package cryptomessenger.server.service.message;
2+
3+
public interface NewMessageListener {
4+
5+
void onNewMessage(Message message);
6+
}

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>org.example</groupId>
88
<artifactId>crypto-messenger</artifactId>
9-
<version>1.0.0</version>
9+
<version>1.1.0</version>
1010
<packaging>pom</packaging>
1111

1212
<modules>

0 commit comments

Comments
 (0)