diff --git a/web/src/client/index.js b/web/src/client/index.js index 25b4a70d9..5e161ef5f 100644 --- a/web/src/client/index.js +++ b/web/src/client/index.js @@ -22,17 +22,17 @@ // @ts-check -import { HTTPClient } from "./http"; +import { WSClient } from "./ws"; /** * @typedef {object} InstallerClient - * @property {() => import("./http").WSClient} ws - Agama WebSocket client. * @property {() => boolean} isConnected - determines whether the client is connected * @property {() => boolean} isRecoverable - determines whether the client is recoverable after disconnected * @property {(handler: () => void) => (() => void)} onConnect - registers a handler to run * @property {(handler: () => void) => (() => void)} onDisconnect - registers a handler to run * when the connection is lost. It returns a function to deregister the * handler. + * @property {(handler: (any) => void) => (() => void)} onEvent - registers a handler to run on events */ /** @@ -42,18 +42,20 @@ import { HTTPClient } from "./http"; * @return {InstallerClient} */ const createClient = (url) => { - const client = new HTTPClient(url); - // TODO: unify with the manager client + url.hash = ""; + url.pathname = url.pathname.concat("api/ws"); + url.protocol = url.protocol === "http:" ? "ws" : "wss"; + const ws = new WSClient(url); - const isConnected = () => client.ws().isConnected() || false; - const isRecoverable = () => !!client.ws().isRecoverable(); + const isConnected = () => ws.isConnected() || false; + const isRecoverable = () => !!ws.isRecoverable(); return { isConnected, isRecoverable, - onConnect: (handler) => client.ws().onOpen(handler), - onDisconnect: (handler) => client.ws().onClose(handler), - ws: () => client.ws(), + onConnect: (handler) => ws.onOpen(handler), + onDisconnect: (handler) => ws.onClose(handler), + onEvent: (handler) => ws.onEvent(handler), }; }; diff --git a/web/src/client/http.js b/web/src/client/ws.js similarity index 58% rename from web/src/client/http.js rename to web/src/client/ws.js index ae148bfea..b63c8e2a5 100644 --- a/web/src/client/http.js +++ b/web/src/client/ws.js @@ -230,164 +230,4 @@ class WSClient { } } -/** - * Agama HTTP API client. - */ -class HTTPClient { - /** - * @param {URL} url - URL of the HTTP API. - */ - constructor(url) { - const httpUrl = new URL(url.toString()); - httpUrl.pathname = url.pathname.concat("api"); - this.baseUrl = httpUrl.toString(); - this.url = url; - } - - /** - * Return the websocket client - * - * The WSClient is lazily created in the first call of this method. - * - * @return {WSClient} - */ - ws() { - if (this._ws) return this._ws; - - const wsUrl = new URL(this.url.toString()); - wsUrl.pathname = wsUrl.pathname.concat("api/ws"); - wsUrl.protocol = this.url.protocol === "http:" ? "ws" : "wss"; - this._ws = new WSClient(wsUrl); - return this._ws; - } - - /** - * @param {string} url - Endpoint URL (e.g., "/l10n/config"). - * @return {Promise} Server response. - */ - async get(url) { - const response = await fetch(`${this.baseUrl}${url}`, { - headers: { - "Content-Type": "application/json", - }, - }); - return response; - } - - /** - * @param {string} url - Endpoint URL (e.g., "/l10n/config"). - * @param {object} data - Data to submit - * @return {Promise} Server response. - */ - async post(url, data) { - const response = await fetch(`${this.baseUrl}${url}`, { - method: "POST", - body: JSON.stringify(data), - headers: { - "Content-Type": "application/json", - }, - }); - - return response; - } - - /** - * @param {string} url - Endpoint URL (e.g., "/l10n/config"). - * @param {object} data - Data to submit - * @return {Promise} Server response. - */ - async put(url, data) { - const response = await fetch(`${this.baseUrl}${url}`, { - method: "PUT", - body: JSON.stringify(data), - headers: { - "Content-Type": "application/json", - }, - }); - - return response; - } - - /** - * @param {string} url - Endpoint URL (e.g., "/l10n/config"). - * @return {Promise} Server response. - */ - async delete(url) { - const response = await fetch(`${this.baseUrl}${url}`, { - method: "DELETE", - }); - - return response; - } - - /** - * @param {string} url - Endpoint URL (e.g., "/l10n/config"). - * @param {object} data - Data to submit - * @return {Promise} Server response. - */ - async patch(url, data) { - const response = await fetch(`${this.baseUrl}${url}`, { - method: "PATCH", - body: JSON.stringify(data), - headers: { - "Content-Type": "application/json", - }, - }); - - return response; - } - - /** - * Registers a handler for being called when the socket is closed - * - * @param {() => void} func - Handler function to register. - * @return {RemoveFn} - Function to remove the handler. - */ - onClose(func) { - return this.ws().onClose(() => { - func(); - }); - } - - /** - * - * Registers a handler for being called when there is some error in the socket - * - * @param {(event: Object) => void} func - Handler function to register. - * @return {RemoveFn} - Function to remove the handler. - */ - onError(func) { - return this.ws().onError((event) => { - func(event); - }); - } - - /** - * Registers a handler for being called when the socket is opened - * - * @param {(event: Object) => void} func - Handler function to register. - * @return {RemoveFn} - Function to remove the handler. - */ - onOpen(func) { - return this.ws().onOpen((event) => { - func(event); - }); - } - - /** - * Registers a handler for a given type of events. - * - * @param {string} type - Event type (e.g., "LocaleChanged"). - * @param {(event: Object) => void} func - Handler function to register. - * @return {RemoveFn} - Function to remove the handler. - */ - onEvent(type, func) { - return this.ws().onEvent((event) => { - if (event.type === type) { - func(event); - } - }); - } -} - -export { HTTPClient, WSClient }; +export { WSClient }; diff --git a/web/src/queries/issues.ts b/web/src/queries/issues.ts index e848ab32d..1e8787831 100644 --- a/web/src/queries/issues.ts +++ b/web/src/queries/issues.ts @@ -71,7 +71,7 @@ const useIssuesChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "IssuesChanged") { const path = event.path; const scope = scopesFromPath[path]; diff --git a/web/src/queries/l10n.ts b/web/src/queries/l10n.ts index 57b6d6adf..17438d0ff 100644 --- a/web/src/queries/l10n.ts +++ b/web/src/queries/l10n.ts @@ -86,7 +86,7 @@ const useL10nConfigChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "L10nConfigChanged") { queryClient.invalidateQueries({ queryKey: ["l10n/config"] }); } diff --git a/web/src/queries/network.ts b/web/src/queries/network.ts index ed660acf4..b3da56afc 100644 --- a/web/src/queries/network.ts +++ b/web/src/queries/network.ts @@ -222,7 +222,7 @@ const useNetworkConfigChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "NetworkChange") { if (event.deviceRemoved || event.deviceAdded) { queryClient.invalidateQueries({ queryKey: ["network"] }); diff --git a/web/src/queries/progress.ts b/web/src/queries/progress.ts index 12ee1d139..4ea2701ef 100644 --- a/web/src/queries/progress.ts +++ b/web/src/queries/progress.ts @@ -75,7 +75,7 @@ const useProgressChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "Progress") { const service = servicesMap[event.service]; if (!service) { diff --git a/web/src/queries/questions.ts b/web/src/queries/questions.ts index 3ea79e00c..1394df00c 100644 --- a/web/src/queries/questions.ts +++ b/web/src/queries/questions.ts @@ -56,7 +56,7 @@ const useQuestionsChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "QuestionsChanged") { queryClient.invalidateQueries({ queryKey: ["questions"] }); } diff --git a/web/src/queries/software.ts b/web/src/queries/software.ts index 90d18420a..9ad08b7ec 100644 --- a/web/src/queries/software.ts +++ b/web/src/queries/software.ts @@ -184,7 +184,7 @@ const useProductChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "ProductChanged") { queryClient.invalidateQueries({ queryKey: ["software/config"] }); } @@ -204,7 +204,7 @@ const useProposalChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "SoftwareProposalChanged") { queryClient.invalidateQueries({ queryKey: ["software/proposal"] }); } diff --git a/web/src/queries/status.ts b/web/src/queries/status.ts index d14b833c0..7aa91dc85 100644 --- a/web/src/queries/status.ts +++ b/web/src/queries/status.ts @@ -61,7 +61,7 @@ const useInstallerStatusChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { const { type } = event; const data = queryClient.getQueryData(["status"]) as object; if (!data) { diff --git a/web/src/queries/storage.ts b/web/src/queries/storage.ts index 8009a81cc..ee58f1c77 100644 --- a/web/src/queries/storage.ts +++ b/web/src/queries/storage.ts @@ -340,7 +340,7 @@ const useDeprecatedChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent(({ type, dirty: value }) => { + return client.onEvent(({ type, dirty: value }) => { if (type === "DevicesDirty") { queryClient.setQueryData(deprecatedQuery.queryKey, value); } diff --git a/web/src/queries/storage/dasd.ts b/web/src/queries/storage/dasd.ts index bc5f4e737..e044ba0ad 100644 --- a/web/src/queries/storage/dasd.ts +++ b/web/src/queries/storage/dasd.ts @@ -81,7 +81,7 @@ const useDASDFormatJobChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices switch (event.type) { case "DASDFormatJobChanged": { @@ -132,7 +132,7 @@ const useDASDDevicesChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { switch (event.type) { case "DASDDeviceAdded": { const device: DASDDevice = event.device; diff --git a/web/src/queries/storage/iscsi.ts b/web/src/queries/storage/iscsi.ts index 57d33c894..724e93f1e 100644 --- a/web/src/queries/storage/iscsi.ts +++ b/web/src/queries/storage/iscsi.ts @@ -67,7 +67,7 @@ const useInitiatorChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent(({ type, name, ibft }) => { + return client.onEvent(({ type, name, ibft }) => { if (type !== "ISCSIInitiatorChanged") return; queryClient.setQueryData(initiatorQuery.queryKey, (oldData: ISCSIInitiator | undefined) => { @@ -102,7 +102,7 @@ const useNodesChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent(({ type, node }) => { + return client.onEvent(({ type, node }) => { if (!["ISCSINodeAdded", "ISCSINodeChanged", "ISCSINodeRemoved"].includes(type)) { return; } diff --git a/web/src/queries/storage/zfcp.ts b/web/src/queries/storage/zfcp.ts index 9b73d7561..b8c180c0e 100644 --- a/web/src/queries/storage/zfcp.ts +++ b/web/src/queries/storage/zfcp.ts @@ -94,7 +94,7 @@ const useZFCPControllersChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent(({ type, device }) => { + return client.onEvent(({ type, device }) => { if ( !["ZFCPControllerAdded", "ZFCPControllerChanged", "ZFCPControllerRemoved"].includes(type) ) { @@ -132,7 +132,7 @@ const useZFCPDisksChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent(({ type, device }) => { + return client.onEvent(({ type, device }) => { if (!["ZFCPDiskAdded", "ZFCPDiskChanged", "ZFCPDiskRemoved"].includes(type)) { return; } diff --git a/web/src/queries/users.ts b/web/src/queries/users.ts index 4d501b404..e9df67f06 100644 --- a/web/src/queries/users.ts +++ b/web/src/queries/users.ts @@ -81,7 +81,7 @@ const useFirstUserChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "FirstUserChanged") { const { fullName, userName, password, autologin, data } = event; queryClient.setQueryData(["users", "firstUser"], { @@ -131,7 +131,7 @@ const useRootUserChanges = () => { React.useEffect(() => { if (!client) return; - return client.ws().onEvent((event) => { + return client.onEvent((event) => { if (event.type === "RootChanged") { const { password, sshkey } = event; queryClient.setQueryData(["users", "root"], (oldRoot: RootUser) => {