From 5541cc9fbe7c990d77d8604a50b4561f617e1aec Mon Sep 17 00:00:00 2001 From: Athou Date: Thu, 21 Dec 2023 20:27:30 +0100 Subject: [PATCH] websocket can now be disabled, the websocket ping interval and the tree reload interval can now be configured (#1132) --- commafeed-client/src/app/types.ts | 3 ++ commafeed-client/src/hooks/useWebSocket.ts | 41 +++++++++++-------- commafeed-client/src/pages/app/Layout.tsx | 14 ++++--- commafeed-server/config.dev.yml | 9 ++++ commafeed-server/config.yml.example | 9 ++++ .../com/commafeed/CommaFeedConfiguration.java | 10 ++++- .../commafeed/frontend/model/ServerInfo.java | 9 ++++ .../frontend/resource/ServerREST.java | 3 ++ .../commafeed/integration/rest/ServerIT.java | 22 ++++++++++ .../src/test/resources/config.test.yml | 9 ++++ 10 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 commafeed-server/src/test/java/com/commafeed/integration/rest/ServerIT.java diff --git a/commafeed-client/src/app/types.ts b/commafeed-client/src/app/types.ts index c4900b0a..88b6d50d 100644 --- a/commafeed-client/src/app/types.ts +++ b/commafeed-client/src/app/types.ts @@ -190,6 +190,9 @@ export interface ServerInfo { googleAnalyticsCode?: string smtpEnabled: boolean demoAccountEnabled: boolean + websocketEnabled: boolean + websocketPingInterval: number + treeReloadInterval: number } export interface Settings { diff --git a/commafeed-client/src/hooks/useWebSocket.ts b/commafeed-client/src/hooks/useWebSocket.ts index 1615aed7..1d449ecd 100644 --- a/commafeed-client/src/hooks/useWebSocket.ts +++ b/commafeed-client/src/hooks/useWebSocket.ts @@ -1,32 +1,37 @@ import { setWebSocketConnected } from "app/slices/server" import { reloadTree } from "app/slices/tree" -import { useAppDispatch } from "app/store" +import { useAppDispatch, useAppSelector } from "app/store" import { useEffect } from "react" import WebsocketHeartbeatJs from "websocket-heartbeat-js" export const useWebSocket = () => { + const websocketEnabled = useAppSelector(state => state.server.serverInfos?.websocketEnabled) + const websocketPingInterval = useAppSelector(state => state.server.serverInfos?.websocketPingInterval) const dispatch = useAppDispatch() useEffect(() => { - const currentUrl = new URL(window.location.href) - const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss" - const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}/ws` + let ws: WebsocketHeartbeatJs | undefined - const ws = new WebsocketHeartbeatJs({ - url: wsUrl, - pingMsg: "ping", - // ping interval, just under a minute to prevent firewalls from closing idle connections - pingTimeout: 55000, - }) - ws.onopen = () => dispatch(setWebSocketConnected(true)) - ws.onclose = () => dispatch(setWebSocketConnected(false)) - ws.onmessage = event => { - const { data } = event - if (typeof data === "string") { - if (data.startsWith("new-feed-entries:")) dispatch(reloadTree()) + if (websocketEnabled && websocketPingInterval) { + const currentUrl = new URL(window.location.href) + const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss" + const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}/ws` + + ws = new WebsocketHeartbeatJs({ + url: wsUrl, + pingMsg: "ping", + pingTimeout: websocketPingInterval, + }) + ws.onopen = () => dispatch(setWebSocketConnected(true)) + ws.onclose = () => dispatch(setWebSocketConnected(false)) + ws.onmessage = event => { + const { data } = event + if (typeof data === "string") { + if (data.startsWith("new-feed-entries:")) dispatch(reloadTree()) + } } } - return () => ws.close() - }, [dispatch]) + return () => ws?.close() + }, [dispatch, websocketEnabled, websocketPingInterval]) } diff --git a/commafeed-client/src/pages/app/Layout.tsx b/commafeed-client/src/pages/app/Layout.tsx index 783e6fbb..ad95fa13 100644 --- a/commafeed-client/src/pages/app/Layout.tsx +++ b/commafeed-client/src/pages/app/Layout.tsx @@ -95,6 +95,7 @@ export default function Layout(props: LayoutProps) { const mobile = useMobile() const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) + const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) const sidebarHidden = props.sidebarWidth === 0 const dispatch = useAppDispatch() useWebSocket() @@ -110,12 +111,15 @@ export default function Layout(props: LayoutProps) { }, [dispatch]) useEffect(() => { - // reload tree periodically if not receiving websocket events - const timer = setInterval(() => { - if (!webSocketConnected) dispatch(reloadTree()) - }, 30000) + let timer: number | undefined + + if (!webSocketConnected && treeReloadInterval) { + // reload tree periodically if not receiving websocket events + timer = window.setInterval(() => dispatch(reloadTree()), treeReloadInterval) + } + return () => clearInterval(timer) - }, [dispatch, webSocketConnected]) + }, [dispatch, webSocketConnected, treeReloadInterval]) const burger = (
diff --git a/commafeed-server/config.dev.yml b/commafeed-server/config.dev.yml index f99b2490..76f44e88 100644 --- a/commafeed-server/config.dev.yml +++ b/commafeed-server/config.dev.yml @@ -78,6 +78,15 @@ app: # user-agent string that will be used by the http client, leave empty for the default one userAgent: + # enable websocket connection so the server can notify the web client that there are new entries for your feeds + websocketEnabled: true + + # interval at which the client will send a ping message on the websocket to keep the connection alive + websocketPingInterval: 15m + + # if websocket is disabled or the connection is lost, the client will reload the feed tree at this interval + treeReloadInterval: 30s + # Database connection # ------------------- # for MariaDB diff --git a/commafeed-server/config.yml.example b/commafeed-server/config.yml.example index 9a0051ba..680e35df 100644 --- a/commafeed-server/config.yml.example +++ b/commafeed-server/config.yml.example @@ -79,6 +79,15 @@ app: # user-agent string that will be used by the http client, leave empty for the default one userAgent: + # enable websocket connection so the server can notify the web client that there are new entries for your feeds + websocketEnabled: true + + # interval at which the client will send a ping message on the websocket to keep the connection alive + websocketPingInterval: 15m + + # if websocket is disabled or the connection is lost, the client will reload the feed tree at this interval + treeReloadInterval: 30s + # Database connection # ------------------- # for MariaDB diff --git a/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java b/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java index 0e3be9c2..1134081a 100644 --- a/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java +++ b/commafeed-server/src/main/java/com/commafeed/CommaFeedConfiguration.java @@ -13,6 +13,7 @@ import be.tomcools.dropwizard.websocket.WebsocketBundleConfiguration; import be.tomcools.dropwizard.websocket.WebsocketConfiguration; import io.dropwizard.core.Configuration; import io.dropwizard.db.DataSourceFactory; +import io.dropwizard.util.Duration; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -62,8 +63,7 @@ public class CommaFeedConfiguration extends Configuration implements WebsocketBu @Override public WebsocketConfiguration getWebsocketConfiguration() { WebsocketConfiguration config = new WebsocketConfiguration(); - // the client sends ping messages every minute, so we can close idle connections a little bit after that - config.setMaxSessionIdleTimeout(90000L); + config.setMaxSessionIdleTimeout(getApplicationSettings().getWebsocketPingInterval().toMilliseconds() + 10000); return config; } @@ -164,6 +164,12 @@ public class CommaFeedConfiguration extends Configuration implements WebsocketBu private String userAgent; + private Boolean websocketEnabled = true; + + private Duration websocketPingInterval = Duration.minutes(15); + + private Duration treeReloadInterval = Duration.seconds(30); + public Date getUnreadThreshold() { int keepStatusDays = getKeepStatusDays(); return keepStatusDays > 0 ? DateUtils.addDays(new Date(), -1 * keepStatusDays) : null; diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/model/ServerInfo.java b/commafeed-server/src/main/java/com/commafeed/frontend/model/ServerInfo.java index f28d79d0..0eee703b 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/model/ServerInfo.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/model/ServerInfo.java @@ -32,4 +32,13 @@ public class ServerInfo implements Serializable { @Schema(requiredMode = RequiredMode.REQUIRED) private boolean demoAccountEnabled; + @Schema(requiredMode = RequiredMode.REQUIRED) + private boolean websocketEnabled; + + @Schema(requiredMode = RequiredMode.REQUIRED) + private long websocketPingInterval; + + @Schema(requiredMode = RequiredMode.REQUIRED) + private long treeReloadInterval; + } diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java index 0307a3bc..7847f155 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/ServerREST.java @@ -56,6 +56,9 @@ public class ServerREST { infos.setGoogleAnalyticsCode(config.getApplicationSettings().getGoogleAnalyticsTrackingCode()); infos.setSmtpEnabled(StringUtils.isNotBlank(config.getApplicationSettings().getSmtpHost())); infos.setDemoAccountEnabled(config.getApplicationSettings().getCreateDemoAccount()); + infos.setWebsocketEnabled(config.getApplicationSettings().getWebsocketEnabled()); + infos.setWebsocketPingInterval(config.getApplicationSettings().getWebsocketPingInterval().toMilliseconds()); + infos.setTreeReloadInterval(config.getApplicationSettings().getTreeReloadInterval().toMilliseconds()); return Response.ok(infos).build(); } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/ServerIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/ServerIT.java new file mode 100644 index 00000000..5ec64966 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/ServerIT.java @@ -0,0 +1,22 @@ +package com.commafeed.integration.rest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.commafeed.frontend.model.ServerInfo; +import com.commafeed.integration.BaseIT; + +public class ServerIT extends BaseIT { + + @Test + void getServerInfos() { + ServerInfo serverInfos = getClient().target(getApiBaseUrl() + "server/get").request().get(ServerInfo.class); + Assertions.assertTrue(serverInfos.isAllowRegistrations()); + Assertions.assertTrue(serverInfos.isSmtpEnabled()); + Assertions.assertTrue(serverInfos.isDemoAccountEnabled()); + Assertions.assertTrue(serverInfos.isWebsocketEnabled()); + Assertions.assertEquals(900000, serverInfos.getWebsocketPingInterval()); + Assertions.assertEquals(30000, serverInfos.getTreeReloadInterval()); + + } +} diff --git a/commafeed-server/src/test/resources/config.test.yml b/commafeed-server/src/test/resources/config.test.yml index 2fddc93e..2615483f 100644 --- a/commafeed-server/src/test/resources/config.test.yml +++ b/commafeed-server/src/test/resources/config.test.yml @@ -78,6 +78,15 @@ app: # user-agent string that will be used by the http client, leave empty for the default one userAgent: + # enable websocket connection so the server can notify the web client that there are new entries for your feeds + websocketEnabled: true + + # interval at which the client will send a ping message on the websocket to keep the connection alive + websocketPingInterval: 15m + + # if websocket is disabled or the connection is lost, the client will reload the feed tree at this interval + treeReloadInterval: 30s + # Database connection # ------------------- # for MariaDB