Skip to content

Conversation

@marioserrano09
Copy link
Contributor

No description provided.

Copilot AI review requested due to automatic review settings December 3, 2025 19:40
@marioserrano09 marioserrano09 merged commit cd5e14b into main Dec 3, 2025
3 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the WebSocket handler utility class by implementing bidirectional heartbeat support (PING/PONG), refactoring session management, and bumping the framework version to 5.4.10. The changes add server-side heartbeat broadcasting capabilities and improve error handling when sending WebSocket messages.

Key changes:

  • Added bidirectional PING/PONG heartbeat mechanism between client and server
  • Refactored WebSocket session management with a new DeskstopWebSocketSession record wrapper
  • Improved error handling with separate IOException handling and session cleanup
  • Bumped all module versions from 5.4.9 to 5.4.10

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
zk/src/main/resources/static/dynamia-tools/js/dynamia-tools-ws.js Increased keep-alive ping interval from 30s to 60s; added server PING handling with PONG response
zk/src/main/java/tools/dynamia/zk/websocket/WebSocketPushSender.java Added broadcastHeartbeat() method; improved error handling with IOException separation; changed getHandler() visibility to public
zk/src/main/java/tools/dynamia/zk/websocket/WebSocketGlobalCommandHandler.java Refactored session storage with DeskstopWebSocketSession record; added PONG handling; simplified session lookup logic; added closeSession() method
integration/src/main/java/tools/dynamia/integration/scheduling/SchedulerUtil.java Renamed method from scheduleWithFixedDelay to scheduleAtFixedRate with corrected documentation
Multiple pom.xml files Version bump from 5.4.9 to 5.4.10 across all modules

Comment on lines +80 to 83
LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e);
handler.closeSession(desktop);
} catch (Exception e) {
LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error: "Dekstop" should be "Desktop"

Suggested change
LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e);
handler.closeSession(desktop);
} catch (Exception e) {
LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e);
LOGGER.error("IO Error sending push command '" + command + "' to Desktop: " + desktop, e);
handler.closeSession(desktop);
} catch (Exception e) {
LOGGER.error("Error sending push command '" + command + "' to Desktop: " + desktop, e);

Copilot uses AI. Check for mistakes.
Comment on lines +80 to 83
LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e);
handler.closeSession(desktop);
} catch (Exception e) {
LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error: "Dekstop" should be "Desktop"

Suggested change
LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e);
handler.closeSession(desktop);
} catch (Exception e) {
LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e);
LOGGER.error("IO Error sending push command '" + command + "' to Desktop: " + desktop, e);
handler.closeSession(desktop);
} catch (Exception e) {
LOGGER.error("Error sending push command '" + command + "' to Desktop: " + desktop, e);

Copilot uses AI. Check for mistakes.
log("Associating desktop " + desktopId + " with ws session " + session.getId());
desktops.put(desktopId, session.getId());
log("Associating desktop " + payload + " with ws session " + session.getId());
sessions.put(session.getId(), new DeskstopWebSocketSession(payload, session));
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession"

Copilot uses AI. Check for mistakes.
}
}
return session;
public DeskstopWebSocketSession findSession(Desktop desktop) {
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession"

Copilot uses AI. Check for mistakes.
*/
Collection<WebSocketSession> getAllSessions() {
return sessions.values();
public Map<String, DeskstopWebSocketSession> getSessions() {
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession"

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +102
var oldSession = sessions.get(payload);
if (oldSession != null) {
oldSession.close(CloseStatus.NORMAL);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential bug in session replacement logic: When a desktop re-registers, the old session is retrieved using sessions.get(payload) where payload is the desktop ID, but sessions is now a map keyed by session ID, not desktop ID. This will always return null, preventing the cleanup of old sessions when a desktop reconnects. The old logic correctly looked up sessions by desktop ID first, then session ID.

Suggested change
var oldSession = sessions.get(payload);
if (oldSession != null) {
oldSession.close(CloseStatus.NORMAL);
DeskstopWebSocketSession oldSession = null;
String oldSessionId = null;
for (Map.Entry<String, DeskstopWebSocketSession> entry : sessions.entrySet()) {
if (payload.equals(entry.getValue().getDesktopId())) {
oldSession = entry.getValue();
oldSessionId = entry.getKey();
break;
}
}
if (oldSession != null) {
oldSession.close(CloseStatus.NORMAL);
sessions.remove(oldSessionId);

Copilot uses AI. Check for mistakes.
broadcastCommand("PING");
}


Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] API design: Changing the visibility of getHandler() from private to public makes an internal utility method part of the public API without documentation explaining when external code should use it. Consider adding Javadoc to explain the purpose and usage of this method, or evaluate if this visibility change is necessary.

Suggested change
/**
* Returns the global {@link WebSocketGlobalCommandHandler} instance used to manage all WebSocket sessions and commands.
* <p>
* This method allows external code to access the handler for advanced operations, such as querying sessions or sending custom commands.
* Use with caution, as direct manipulation of the handler may affect all connected clients.
*
* @return the global {@link WebSocketGlobalCommandHandler} instance, or {@code null} if not available
*
* Example:
* <pre>{@code
* WebSocketGlobalCommandHandler handler = WebSocketPushSender.getHandler();
* if (handler != null) {
* handler.getSessions().forEach((desktop, session) -> {
* // Custom logic
* });
* }
* }</pre>
*/

Copilot uses AI. Check for mistakes.
private final Map<String, String> desktops = new ConcurrentHashMap<>();
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
// Map of active WebSocket sessions by session ID
private final Map<String, DeskstopWebSocketSession> sessions = new ConcurrentHashMap<>();
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession". This typo appears in the type declaration and affects the entire class structure.

Copilot uses AI. Check for mistakes.
} catch (IOException e) {
LOGGER.error("Error sending command " + command + " to WS Session: " + s);
LOGGER.error("IO Error sending command " + command + " to WS Session: " + s, e);
s.close(CloseStatus.NORMAL);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential resource leak: When an IOException occurs during broadcast, the session is closed with CloseStatus.NORMAL, but it's not removed from the sessions map. This differs from the pattern in the sendPushCommand method where handler.closeSession(desktop) is called to properly clean up the session. Consider using a similar cleanup approach here to ensure the session is removed from the map.

Suggested change
s.close(CloseStatus.NORMAL);
// Properly clean up the session by removing it from the handler
handler.closeSession(s.getDesktop());

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +161
public Map<String, DeskstopWebSocketSession> getSessions() {
return sessions;
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API design: The return type has changed from Collection<WebSocketSession> to Map<String, DeskstopWebSocketSession>, which is a breaking change for any code that calls this method. The new type exposes internal session IDs and the misspelled DeskstopWebSocketSession type. Consider whether this should return an unmodifiable collection or view to prevent external modification of the internal session map.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants