diff --git a/rust/agama-server/src/web.rs b/rust/agama-server/src/web.rs index 8904565ff7..abb6d34349 100644 --- a/rust/agama-server/src/web.rs +++ b/rust/agama-server/src/web.rs @@ -224,6 +224,7 @@ async fn run_events_monitor(dbus: zbus::Connection, events: EventsSender) -> Res tokio::pin!(stream); let e = events.clone(); while let Some((_, event)) = stream.next().await { + tracing::info!("event: {:?}", &event); _ = e.send(event); } Ok(()) diff --git a/rust/agama-server/src/web/ws.rs b/rust/agama-server/src/web/ws.rs index 5c6ce10776..10d17c6270 100644 --- a/rust/agama-server/src/web/ws.rs +++ b/rust/agama-server/src/web/ws.rs @@ -40,7 +40,10 @@ async fn handle_socket(mut socket: WebSocket, events: EventsSender) { let mut rx = events.subscribe(); while let Ok(msg) = rx.recv().await { if let Ok(json) = serde_json::to_string(&msg) { - _ = socket.send(Message::Text(json)).await; + if socket.send(Message::Text(json)).await.is_err() { + tracing::info!("ws: client disconnected"); + return; + } } } } diff --git a/rust/package/agama.changes b/rust/package/agama.changes index e208e4c4a3..58bf5fb564 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Thu Jan 16 13:10:54 UTC 2025 - Imobach Gonzalez Sosa + +- Stop the WebSocket handler when the client is disconnected + (gh#agama-project/agama#1909). +- Log the events. + ------------------------------------------------------------------- Thu Jan 16 09:32:00 UTC 2025 - Martin Vidner diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index b856e7cb7c..06367517d0 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Jan 16 13:11:06 UTC 2025 - Imobach Gonzalez Sosa + +- Display error messages during package installation + (gh#agama-project/agama#1909). + ------------------------------------------------------------------- Mon Jan 13 11:11:49 UTC 2025 - David Diaz diff --git a/web/src/client/index.ts b/web/src/client/index.ts index 97826e5cd5..41f60c26b5 100644 --- a/web/src/client/index.ts +++ b/web/src/client/index.ts @@ -20,11 +20,10 @@ * find current contact information at www.suse.com. */ -import { WSClient } from "./ws"; +import { WSClient, EventHandlerFn, ErrorHandlerFn } from "./ws"; type VoidFn = () => void; type BooleanFn = () => boolean; -type EventHandlerFn = (event) => void; export type InstallerClient = { /** Whether the client is connected. */ @@ -37,10 +36,15 @@ export type InstallerClient = { */ onConnect: (handler: VoidFn) => VoidFn; /** - * Registers a handler to run when connection is lost. It returns a function + * Registers a handler to run when connection is closed. It returns a function * for deregistering the handler. */ - onDisconnect: (handler: VoidFn) => VoidFn; + onClose: (handler: VoidFn) => VoidFn; + /** + * Registers a handler to run when there is an error. It returns a function + * for deregistering the handler. + */ + onError: (handler: ErrorHandlerFn) => VoidFn; /** * Registers a handler to run on events. It returns a function for * deregistering the handler. @@ -66,7 +70,8 @@ const createClient = (url: URL): InstallerClient => { isConnected, isRecoverable, onConnect: (handler: VoidFn) => ws.onOpen(handler), - onDisconnect: (handler: VoidFn) => ws.onClose(handler), + onClose: (handler: VoidFn) => ws.onClose(handler), + onError: (handler: ErrorHandlerFn) => ws.onError(handler), onEvent: (handler: EventHandlerFn) => ws.onEvent(handler), }; }; diff --git a/web/src/client/ws.ts b/web/src/client/ws.ts index 608001f2cd..529c1e3c43 100644 --- a/web/src/client/ws.ts +++ b/web/src/client/ws.ts @@ -22,7 +22,8 @@ type RemoveFn = () => void; type BaseHandlerFn = () => void; -type EventHandlerFn = (event) => void; +export type EventHandlerFn = (event) => void; +export type ErrorHandlerFn = (error: object) => void; /** * Enum for the WebSocket states. @@ -53,7 +54,7 @@ class WSClient { handlers: { open: Array; close: Array; - error: Array; + error: Array; events: Array; }; @@ -109,14 +110,14 @@ class WSClient { }; client.onclose = () => { - console.log(`WebSocket closed`); + console.log("WebSocket closed"); this.dispatchCloseEvent(); this.timeout = setTimeout(() => this.connect(this.reconnectAttempts + 1), ATTEMPT_INTERVAL); }; - client.onerror = (e) => { - console.error("WebSocket error:", e); - this.dispatchErrorEvent(); + client.onerror = (error) => { + console.error("WebSocket error:", error); + this.dispatchErrorEvent(error); }; return client; @@ -179,7 +180,7 @@ class WSClient { * * The handler is executed when an error is reported by the socket. */ - onError(func: BaseHandlerFn): RemoveFn { + onError(func: ErrorHandlerFn): RemoveFn { this.handlers.error.push(func); return () => { @@ -214,8 +215,8 @@ class WSClient { * * Dispatchs an error event by running all its handlers. */ - dispatchErrorEvent() { - this.handlers.error.forEach((f) => f()); + dispatchErrorEvent(error) { + this.handlers.error.forEach((f) => f(error)); } /** diff --git a/web/src/context/app.tsx b/web/src/context/app.tsx index 70686fde79..339450901f 100644 --- a/web/src/context/app.tsx +++ b/web/src/context/app.tsx @@ -24,8 +24,28 @@ import React from "react"; import { InstallerClientProvider } from "./installer"; import { InstallerL10nProvider } from "./installerL10n"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { localConnection } from "~/utils"; -const queryClient = new QueryClient(); +// Determines which "network mode" should Tanstack Query use +// +// When running on a local connection, we assume that the server is always +// available so Tanstack Query is expected to perform all the request, no +// matter whether the network is available on not. +// +// For remote connections, let's use the default "online" mode. +// +// See https://tanstack.com/query/latest/docs/framework/react/guides/network-mode +const networkMode = (): "always" | "online" => { + return localConnection() ? "always" : "online"; +}; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + networkMode: networkMode(), + }, + }, +}); /** * Combines all application providers. diff --git a/web/src/context/installer.test.tsx b/web/src/context/installer.test.tsx index 5c4125da1e..7811ffec3d 100644 --- a/web/src/context/installer.test.tsx +++ b/web/src/context/installer.test.tsx @@ -44,7 +44,8 @@ describe("installer context", () => { (createDefaultClient as jest.Mock).mockImplementation(() => { return { onConnect: jest.fn(), - onDisconnect: jest.fn(), + onClose: jest.fn(), + onError: jest.fn(), }; }); }); diff --git a/web/src/context/installer.tsx b/web/src/context/installer.tsx index 6b48662e7d..96fe40e0de 100644 --- a/web/src/context/installer.tsx +++ b/web/src/context/installer.tsx @@ -102,7 +102,7 @@ function InstallerClientProvider({ children, client = null }: InstallerClientPro setError(false); }); - value.onDisconnect(() => { + value.onClose(() => { setConnected(false); setError(!value.isRecoverable()); }); diff --git a/web/src/context/installerL10n.test.tsx b/web/src/context/installerL10n.test.tsx index e97d324920..b8f3153090 100644 --- a/web/src/context/installerL10n.test.tsx +++ b/web/src/context/installerL10n.test.tsx @@ -48,7 +48,8 @@ const client = { isConnected: jest.fn().mockResolvedValue(true), isRecoverable: jest.fn(), onConnect: jest.fn(), - onDisconnect: jest.fn(), + onClose: jest.fn(), + onError: jest.fn(), onEvent: jest.fn(), }; diff --git a/web/src/queries/questions.ts b/web/src/queries/questions.ts index 1394df00c2..871129d2d0 100644 --- a/web/src/queries/questions.ts +++ b/web/src/queries/questions.ts @@ -62,6 +62,14 @@ const useQuestionsChanges = () => { } }); }, [client, queryClient]); + + React.useEffect(() => { + if (!client) return; + + return client.onConnect(() => { + queryClient.invalidateQueries({ queryKey: ["questions"] }); + }); + }, [client, queryClient]); }; /** diff --git a/web/src/test-utils.tsx b/web/src/test-utils.tsx index 39493e288e..962ceafaf4 100644 --- a/web/src/test-utils.tsx +++ b/web/src/test-utils.tsx @@ -93,8 +93,8 @@ const Providers = ({ children, withL10n }) => { // FIXME: workaround to fix the tests. We should inject // the client instead of mocking `createClient`. - if (!client.onDisconnect) { - client.onDisconnect = noop; + if (!client.onClose) { + client.onClose = noop; } if (withL10n) {