SyncMessageHandler.java

package com.oliwier.listmebackend.websocket;

import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

import java.util.UUID;

/**
 * Handles client-initiated STOMP messages.
 *
 * /app/list/{listId}/join  — client announces it's viewing a list
 * /app/list/{listId}/leave — client announces it's leaving a list
 */
@Controller
@RequiredArgsConstructor
public class SyncMessageHandler {

    private final ListSyncBroadcaster broadcaster;
    private final PresenceTracker presenceTracker;

    @MessageMapping("/list/{listId}/join")
    public void join(@DestinationVariable UUID listId, SimpMessageHeaderAccessor accessor) {
        String deviceId = deviceId(accessor);
        if (deviceId == null) return;

        // Store listId in session so we can clean up on disconnect
        accessor.getSessionAttributes().put("lastListId_" + listId, listId.toString());

        broadcaster.broadcastJoin(listId, deviceId);
    }

    @MessageMapping("/list/{listId}/leave")
    public void leave(@DestinationVariable UUID listId, SimpMessageHeaderAccessor accessor) {
        String deviceId = deviceId(accessor);
        if (deviceId == null) return;
        broadcaster.broadcastLeave(listId, deviceId);
    }

    /** Clean up when WebSocket connection drops entirely. */
    @EventListener
    public void onDisconnect(SessionDisconnectEvent event) {
        SimpMessageHeaderAccessor accessor =
                SimpMessageHeaderAccessor.wrap(event.getMessage());
        String deviceId = deviceId(accessor);
        if (deviceId == null) return;

        // Remove from all lists and notify
        presenceTracker.disconnectDevice(deviceId);
        // No broadcast here — the topic subscriptions are already gone
    }

    private String deviceId(SimpMessageHeaderAccessor accessor) {
        if (accessor.getSessionAttributes() == null) return null;
        return (String) accessor.getSessionAttributes()
                .get(DeviceHandshakeInterceptor.DEVICE_ID_ATTR);
    }
}