mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
add websocket support to immediately refresh tree when new entries are available
This commit is contained in:
@@ -9,7 +9,7 @@ import com.commafeed.backend.model.User;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor()
|
||||
@RequiredArgsConstructor
|
||||
public class SessionHelper {
|
||||
|
||||
private static final String SESSION_KEY_USER = "user";
|
||||
@@ -18,15 +18,18 @@ public class SessionHelper {
|
||||
|
||||
public Optional<User> getLoggedInUser() {
|
||||
Optional<HttpSession> session = getSession(false);
|
||||
|
||||
if (session.isPresent()) {
|
||||
User user = (User) session.get().getAttribute(SESSION_KEY_USER);
|
||||
return Optional.ofNullable(user);
|
||||
return getLoggedInUser(session.get());
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Optional<User> getLoggedInUser(HttpSession session) {
|
||||
User user = (User) session.getAttribute(SESSION_KEY_USER);
|
||||
return Optional.ofNullable(user);
|
||||
}
|
||||
|
||||
public void setLoggedInUser(User user) {
|
||||
Optional<HttpSession> session = getSession(true);
|
||||
session.get().setAttribute(SESSION_KEY_USER, user);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.commafeed.frontend.ws;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.websocket.HandshakeResponse;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import javax.websocket.server.ServerEndpointConfig.Configurator;
|
||||
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.frontend.session.SessionHelper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Singleton
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
public class WebSocketConfigurator extends Configurator {
|
||||
|
||||
public static final String SESSIONKEY_USERID = "userId";
|
||||
|
||||
private final WebSocketSessions webSocketSessions;
|
||||
|
||||
@Override
|
||||
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
|
||||
HttpSession httpSession = (HttpSession) request.getHttpSession();
|
||||
if (httpSession != null) {
|
||||
Optional<User> user = SessionHelper.getLoggedInUser(httpSession);
|
||||
if (user.isPresent()) {
|
||||
config.getUserProperties().put(SESSIONKEY_USERID, user.get().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
|
||||
return (T) new WebSocketEndpoint(webSocketSessions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.commafeed.frontend.ws;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
|
||||
public class WebSocketEndpoint extends Endpoint {
|
||||
|
||||
private final WebSocketSessions sessions;
|
||||
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
Long userId = (Long) config.getUserProperties().get(WebSocketConfigurator.SESSIONKEY_USERID);
|
||||
if (userId == null) {
|
||||
reject(session);
|
||||
} else {
|
||||
log.debug("created websocket session for user {}", userId);
|
||||
sessions.add(userId, session);
|
||||
}
|
||||
|
||||
// converting this anonymous inner class to a lambda causes the following error when a message is sent from the client
|
||||
// Unable to find decoder for type <javax.websocket.MessageHandler$Whole>
|
||||
// this error is only visible when registering a listener to ws.onclose on the client
|
||||
session.addMessageHandler(new MessageHandler.Whole<String>() {
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
if ("ping".equals(message)) {
|
||||
session.getAsyncRemote().sendText("pong");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reject(Session session) {
|
||||
try {
|
||||
session.close(new CloseReason(CloseCodes.VIOLATED_POLICY, "unauthorized"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, CloseReason reason) {
|
||||
sessions.remove(session);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.commafeed.frontend.ws;
|
||||
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class WebSocketMessageBuilder {
|
||||
|
||||
public static final String newFeedEntries(FeedSubscription subscription) {
|
||||
return String.format("%s:%s", "new-feed-entries", subscription.getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.commafeed.frontend.ws;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import com.commafeed.backend.model.User;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class WebSocketSessions {
|
||||
|
||||
// a user may have multiple sessions (two tabs, on mobile, ...)
|
||||
private final Map<Long, Set<Session>> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
public void add(Long userId, Session session) {
|
||||
sessions.computeIfAbsent(userId, v -> ConcurrentHashMap.newKeySet()).add(session);
|
||||
}
|
||||
|
||||
public void remove(Session session) {
|
||||
sessions.values().forEach(v -> v.removeIf(e -> e.equals(session)));
|
||||
}
|
||||
|
||||
public void sendMessage(User user, String text) {
|
||||
Set<Session> userSessions = sessions.entrySet()
|
||||
.stream()
|
||||
.filter(e -> e.getKey().equals(user.getId()))
|
||||
.flatMap(e -> e.getValue().stream())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
log.debug("sending '{}' to {} users via websocket", text, userSessions.size());
|
||||
for (Session userSession : userSessions) {
|
||||
if (userSession.isOpen()) {
|
||||
userSession.getAsyncRemote().sendText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user