PresenceTracker.java

package com.oliwier.listmebackend.websocket;

import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Thread-safe in-memory tracker of which devices are currently
 * subscribed to (viewing) which lists.
 *
 * listId → Set<deviceId>
 */
@Component
public class PresenceTracker {

    private final Map<UUID, Set<String>> listToDevices = new ConcurrentHashMap<>();

    public void join(UUID listId, String deviceId) {
        listToDevices
                .computeIfAbsent(listId, k -> ConcurrentHashMap.newKeySet())
                .add(deviceId);
    }

    public void leave(UUID listId, String deviceId) {
        Set<String> devices = listToDevices.get(listId);
        if (devices != null) {
            devices.remove(deviceId);
            if (devices.isEmpty()) listToDevices.remove(listId);
        }
    }

    /** Remove a device from ALL lists (called on WebSocket disconnect). */
    public void disconnectDevice(String deviceId) {
        listToDevices.values().forEach(devices -> devices.remove(deviceId));
        listToDevices.entrySet().removeIf(e -> e.getValue().isEmpty());
    }

    public Set<String> getOnlineDevices(UUID listId) {
        return Collections.unmodifiableSet(
                listToDevices.getOrDefault(listId, Collections.emptySet())
        );
    }
}