Skip to content

Commit

Permalink
Add initial distant chat to message window
Browse files Browse the repository at this point in the history
  • Loading branch information
zapek committed Feb 23, 2025
1 parent 50aa391 commit cdf1ff2
Show file tree
Hide file tree
Showing 15 changed files with 460 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2023 by David Gerber - https://zapek.com
* Copyright (c) 2019-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand All @@ -21,6 +21,7 @@

import io.xeres.app.service.MessageService;
import io.xeres.app.xrs.service.chat.ChatRsService;
import io.xeres.common.id.GxsId;
import io.xeres.common.id.LocationIdentifier;
import io.xeres.common.message.MessageType;
import io.xeres.common.message.chat.ChatMessage;
Expand Down Expand Up @@ -68,28 +69,57 @@ public void processPrivateChatMessageFromProducer(@Header(DESTINATION_ID) String
{
case CHAT_PRIVATE_MESSAGE ->
{
log.debug("Received websocket message, sending to peer location: {}, content {}", destinationId, chatMessage);
log.debug("Received private chat websocket message, sending to peer location: {}, content {}", destinationId, chatMessage);
var locationIdentifier = LocationIdentifier.fromString(destinationId);
chatRsService.sendPrivateMessage(locationIdentifier, chatMessage.getContent());
chatMessage.setOwn(true);
messageService.sendToConsumers(BROKER_PREFIX + CHAT_ROOT + CHAT_PRIVATE_DESTINATION, messageType, locationIdentifier, chatMessage);
}
case CHAT_TYPING_NOTIFICATION ->
{
log.debug("Sending chat typing notification...");
log.debug("Sending private chat typing notification...");
Objects.requireNonNull(destinationId);
chatRsService.sendPrivateTypingNotification(LocationIdentifier.fromString(destinationId));
}
case CHAT_AVATAR ->
{
log.debug("Requesting avatar...");
log.debug("Requesting private chat avatar...");
Objects.requireNonNull(destinationId);
chatRsService.sendAvatarRequest(LocationIdentifier.fromString(destinationId));
}
default -> throw new IllegalStateException("Unexpected value: " + messageType);
}
}

@MessageMapping(CHAT_DISTANT_DESTINATION)
public void processDistantChatMessageFromProducer(@Header(DESTINATION_ID) String destinationId, @Header(MESSAGE_TYPE) MessageType messageType, @Payload @Valid ChatMessage chatMessage)
{
switch (messageType)
{
case CHAT_PRIVATE_MESSAGE ->
{
log.debug("Received distant chat websocket message, sending to peer gxsId: {}, content {}", destinationId, chatMessage);
var gxsId = GxsId.fromString(destinationId);
chatRsService.sendPrivateMessage(gxsId, chatMessage.getContent());
chatMessage.setOwn(true);
messageService.sendToConsumers(BROKER_PREFIX + CHAT_ROOT + CHAT_DISTANT_DESTINATION, messageType, gxsId, chatMessage);
}
case CHAT_TYPING_NOTIFICATION ->
{
log.debug("Sending distant chat typing notification...");
Objects.requireNonNull(destinationId);
chatRsService.sendPrivateTypingNotification(GxsId.fromString(destinationId));
}
case CHAT_AVATAR ->
{
log.debug("Requesting distant chat avatar...");
Objects.requireNonNull(destinationId);
chatRsService.sendAvatarRequest(GxsId.fromString(destinationId));
}
default -> throw new IllegalStateException("Unexpected value: " + messageType);
}
}

@MessageMapping(CHAT_ROOM_DESTINATION)
public void processChatRoomMessageFromProducer(@Header(DESTINATION_ID) String destinationId, @Header(MESSAGE_TYPE) MessageType messageType, @Payload @Valid ChatRoomMessage chatRoomMessage)
{
Expand Down
60 changes: 53 additions & 7 deletions app/src/main/java/io/xeres/app/xrs/service/chat/ChatRsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import io.xeres.app.xrs.service.identity.item.IdentityGroupItem;
import io.xeres.common.id.GxsId;
import io.xeres.common.id.Id;
import io.xeres.common.id.Identifier;
import io.xeres.common.id.LocationIdentifier;
import io.xeres.common.message.MessageType;
import io.xeres.common.message.chat.*;
Expand Down Expand Up @@ -288,13 +289,13 @@ public void onGxsTunnelStatusChanged(Location tunnelId, GxsTunnelStatus status)
case UNKNOWN -> log.warn("Don't know how to handle {}", status);
case CAN_TALK ->
{
} // put peer as online
} // XXX: put peer as online
case TUNNEL_DOWN ->
{
} // put peer as offline
} // XXX: put peer as offline
case REMOTELY_CLOSED ->
{
} // put peer as offline
} // XXX: put peer as offline
}
}

Expand Down Expand Up @@ -1018,33 +1019,78 @@ public void sendBroadcastMessage(String message)
/**
* Sends a private message to a peer.
*
* @param locationIdentifier the location id
* @param identifier the identifier (LocationIdentifier or GxsId)
* @param message the message
*/
public void sendPrivateMessage(LocationIdentifier locationIdentifier, String message)
public void sendPrivateMessage(Identifier identifier, String message)
{
switch (identifier)
{
case LocationIdentifier locationIdentifier -> sendPrivateMessageToLocation(locationIdentifier, message);
case GxsId gxsId -> sendPrivateMessageToGxsId(gxsId, message);
default -> throw new IllegalStateException("Unexpected value: " + identifier);
}
}

private void sendPrivateMessageToLocation(LocationIdentifier locationIdentifier, String message)
{
var location = locationService.findLocationByLocationIdentifier(locationIdentifier).orElseThrow();
chatBacklogService.storeOutgoingMessage(location.getLocationIdentifier(), message);
peerConnectionManager.writeItem(location, new ChatMessageItem(message, EnumSet.of(ChatFlags.PRIVATE)), this);
}

private void sendPrivateMessageToGxsId(GxsId gxsId, String message)
{
// XXX
}

/**
* Sends a typing notification for private messages to a peer.
*
* @param locationIdentifier the location id
*/
public void sendPrivateTypingNotification(LocationIdentifier locationIdentifier)
public void sendPrivateTypingNotification(Identifier identifier)
{
switch (identifier)
{
case LocationIdentifier locationIdentifier -> sendPrivateTypingNotificationToLocation(locationIdentifier);
case GxsId gxsId -> sendPrivateTypingNotificationToGxsId(gxsId);
default -> throw new IllegalStateException("Unexpected value: " + identifier);
}
}

private void sendPrivateTypingNotificationToLocation(LocationIdentifier locationIdentifier)
{
var location = locationService.findLocationByLocationIdentifier(locationIdentifier).orElseThrow();
peerConnectionManager.writeItem(location, new ChatStatusItem(MESSAGE_TYPING_CONTENT, EnumSet.of(ChatFlags.PRIVATE)), this);
}

public void sendAvatarRequest(LocationIdentifier locationIdentifier)
private void sendPrivateTypingNotificationToGxsId(GxsId gxsId)
{
// XXX
}

public void sendAvatarRequest(Identifier identifier)
{
switch (identifier)
{
case LocationIdentifier locationIdentifier -> sendAvatarRequestToLocation(locationIdentifier);
case GxsId gxsId -> sendAvatarRequestToGxsId(gxsId);
default -> throw new IllegalStateException("Unexpected value: " + identifier);
}
}

private void sendAvatarRequestToLocation(LocationIdentifier locationIdentifier)
{
var location = locationService.findLocationByLocationIdentifier(locationIdentifier).orElseThrow();
peerConnectionManager.writeItem(location, new ChatMessageItem("", EnumSet.of(ChatFlags.PRIVATE, ChatFlags.REQUEST_AVATAR)), this);
}

private void sendAvatarRequestToGxsId(GxsId gxsId)
{
// XXX
}

/**
* Sets the status message (the one appearing at the top of the profile peer; for example, "I'm eating", "Gone for a walk", etc...).
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023-2024 by David Gerber - https://zapek.com
* Copyright (c) 2023-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand Down Expand Up @@ -67,7 +67,7 @@ class FileServiceTest
private FileService fileService;

@BeforeAll
public static void setErrorLogging()
static void setErrorLogging()
{
LoggingSystem.get(ClassLoader.getSystemClassLoader()).setLogLevel("io.xeres", LogLevel.DEBUG);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
* Copyright (c) 2024-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand Down Expand Up @@ -35,7 +35,7 @@ class FileLeecherTest
private static String tempDir;

@BeforeAll
public static void setup()
static void setup()
{
tempDir = System.getProperty("java.io.tmpdir");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
* Copyright (c) 2024-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand Down Expand Up @@ -29,6 +29,7 @@ public final class MessagePath
public static final String CHAT_PRIVATE_DESTINATION = "/private";
public static final String CHAT_ROOM_DESTINATION = "/room";
public static final String CHAT_BROADCAST_DESTINATION = "/broadcast";
public static final String CHAT_DISTANT_DESTINATION = "/distant";

private MessagePath()
{
Expand All @@ -49,4 +50,9 @@ public static String chatBroadcastDestination()
{
return BROKER_PREFIX + CHAT_ROOT + CHAT_BROADCAST_DESTINATION;
}

public static String chatDistantDestination()
{
return BROKER_PREFIX + CHAT_ROOT + CHAT_DISTANT_DESTINATION;
}
}
4 changes: 2 additions & 2 deletions common/src/main/java/io/xeres/common/message/MessageType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2023 by David Gerber - https://zapek.com
* Copyright (c) 2019-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand Down Expand Up @@ -35,5 +35,5 @@ public enum MessageType
CHAT_ROOM_USER_TIMEOUT,
CHAT_ROOM_INVITE,
CHAT_AVATAR,
CHAT_AVAILABILITY
CHAT_AVAILABILITY,
}
15 changes: 6 additions & 9 deletions ui/src/main/java/io/xeres/ui/PrimaryStageInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
import io.xeres.common.events.ConnectWebSocketsEvent;
import io.xeres.common.properties.StartupProperties;
import io.xeres.ui.client.ProfileClient;
import io.xeres.ui.client.message.ChatFrameHandler;
import io.xeres.ui.client.message.MessageClient;
import io.xeres.ui.client.message.*;
import io.xeres.ui.controller.chat.ChatViewController;
import io.xeres.ui.support.util.UiUtils;
import io.xeres.ui.support.window.WindowManager;
Expand All @@ -37,8 +36,7 @@
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Hooks;

import static io.xeres.common.message.MessagePath.chatPrivateDestination;
import static io.xeres.common.message.MessagePath.chatRoomDestination;
import static io.xeres.common.message.MessagePath.*;
import static io.xeres.common.properties.StartupProperties.Property.ICONIFIED;
import static io.xeres.common.properties.StartupProperties.Property.UI;

Expand Down Expand Up @@ -91,12 +89,11 @@ public void onNetworkReadyEvent(ConnectWebSocketsEvent unused)

// XXX: make sure we're not already connected... I think we can get the event twice when the network is reconfigured

var handler = new ChatFrameHandler(windowManager, chatViewController); // XXX: for now, use the same for both

messageClient
.subscribe(chatPrivateDestination(), handler)
.subscribe(chatRoomDestination(), handler)
// XXX: and don't forget to subscribe to broadcasts one day too
.subscribe(chatPrivateDestination(), new PrivateChatFrameHandler(windowManager))
.subscribe(chatRoomDestination(), new ChatRoomFrameHandler(chatViewController))
.subscribe(chatDistantDestination(), new DistantChatFrameHandler(windowManager))
.subscribe(chatBroadcastDestination(), new BroadcastChatFrameHandler())
.connect();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2019-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
* Xeres is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Xeres is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Xeres. If not, see <http://www.gnu.org/licenses/>.
*/

package io.xeres.ui.client.message;

import io.xeres.common.message.MessageType;
import jakarta.annotation.Nonnull;
import javafx.application.Platform;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;

import java.lang.reflect.Type;

import static io.xeres.common.message.MessageHeaders.MESSAGE_TYPE;

/**
* This handles the incoming broadcast messages from the server to the UI.
* XXX: not used yet
*/
public class BroadcastChatFrameHandler implements StompFrameHandler
{
/**
* Gets the payload type. It's not possible to use null or new Object(). It has to be a class
* that is serializable by jackson.
*
* @param headers the headers
* @return a type
*/
@Nonnull
@Override
public Type getPayloadType(StompHeaders headers)
{
var messageType = MessageType.valueOf(headers.getFirst(MESSAGE_TYPE));
return switch (messageType)
{
case CHAT_BROADCAST_MESSAGE -> Void.class;
default -> throw new IllegalStateException("Unexpected value: " + messageType);
};
}

@Override
public void handleFrame(StompHeaders headers, Object payload)
{
var messageType = MessageType.valueOf(headers.getFirst(MESSAGE_TYPE));
Platform.runLater(() -> {
switch (messageType)
{
case CHAT_BROADCAST_MESSAGE ->
{ /* handled as a notification */ }
}
}
);
}
}
Loading

0 comments on commit cdf1ff2

Please sign in to comment.