1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2025-06-16 13:46:16 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
cc1389536e Fix tests 2024-09-05 13:02:14 +02:00
499be46588 Using socket.io for websocket handling and make reforis configurable
Socket.io wrapper is used to handle websockets now,
this means that websocket logic had to be redone.

Also it is necessary to set `REFORIS_PREFIX` env variable
during the build process. To set the path of backend url.
It was previously fixed to `/reforis`.
2024-09-05 13:02:13 +02:00
13 changed files with 155 additions and 136 deletions

124
package-lock.json generated
View File

@ -18,7 +18,8 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"qrcode.react": "^3.1.0", "qrcode.react": "^3.1.0",
"react-datetime": "^3.2.0", "react-datetime": "^3.2.0",
"react-uid": "^2.3.3" "react-uid": "^2.3.3",
"socket.io-client": "^4.6.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.24.7", "@babel/cli": "^7.24.7",
@ -3583,6 +3584,12 @@
"@sinonjs/commons": "^3.0.0" "@sinonjs/commons": "^3.0.0"
} }
}, },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@testing-library/dom": { "node_modules/@testing-library/dom": {
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-5.6.1.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-5.6.1.tgz",
@ -6372,7 +6379,6 @@
"version": "4.3.5", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@ -6722,6 +6728,28 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/engine.io-client": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.17.1", "version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
@ -13948,8 +13976,7 @@
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"dev": true
}, },
"node_modules/multicast-dns": { "node_modules/multicast-dns": {
"version": "7.2.5", "version": "7.2.5",
@ -16552,6 +16579,34 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"dev": true "dev": true
}, },
"node_modules/socket.io-client": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/sockjs": { "node_modules/sockjs": {
"version": "0.3.24", "version": "0.3.24",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
@ -18360,7 +18415,6 @@
"version": "8.17.1", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
@ -18393,6 +18447,14 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true "dev": true
}, },
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@ -21053,6 +21115,11 @@
"@sinonjs/commons": "^3.0.0" "@sinonjs/commons": "^3.0.0"
} }
}, },
"@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
"@testing-library/dom": { "@testing-library/dom": {
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-5.6.1.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-5.6.1.tgz",
@ -23262,7 +23329,6 @@
"version": "4.3.5", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dev": true,
"requires": { "requires": {
"ms": "2.1.2" "ms": "2.1.2"
} }
@ -23522,6 +23588,23 @@
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"dev": true "dev": true
}, },
"engine.io-client": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="
},
"enhanced-resolve": { "enhanced-resolve": {
"version": "5.17.1", "version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
@ -28947,8 +29030,7 @@
"ms": { "ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"dev": true
}, },
"multicast-dns": { "multicast-dns": {
"version": "7.2.5", "version": "7.2.5",
@ -30877,6 +30959,26 @@
} }
} }
}, },
"socket.io-client": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
}
},
"socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
}
},
"sockjs": { "sockjs": {
"version": "0.3.24", "version": "0.3.24",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
@ -32198,7 +32300,6 @@
"version": "8.17.1", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"requires": {} "requires": {}
}, },
"xml-name-validator": { "xml-name-validator": {
@ -32213,6 +32314,11 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true "dev": true
}, },
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
},
"yallist": { "yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",

View File

@ -23,7 +23,8 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"qrcode.react": "^3.1.0", "qrcode.react": "^3.1.0",
"react-datetime": "^3.2.0", "react-datetime": "^3.2.0",
"react-uid": "^2.3.3" "react-uid": "^2.3.3",
"socket.io-client": "^4.6.1"
}, },
"peerDependencies": { "peerDependencies": {
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",

View File

@ -5,12 +5,10 @@
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
import React, { useRef } from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useFocusTrap } from "../utils/hooks";
export const ALERT_TYPES = Object.freeze({ export const ALERT_TYPES = Object.freeze({
PRIMARY: "primary", PRIMARY: "primary",
SECONDARY: "secondary", SECONDARY: "secondary",
@ -39,15 +37,11 @@ Alert.defaultProps = {
}; };
function Alert({ type, onDismiss, children }) { function Alert({ type, onDismiss, children }) {
const alertRef = useRef();
useFocusTrap(alertRef, !!onDismiss);
return ( return (
<div <div
ref={alertRef}
className={`alert alert-${type} ${ className={`alert alert-${type} ${
onDismiss ? "alert-dismissible" : "" onDismiss ? "alert-dismissible" : ""
}`.trim()} }`.trim()}
role="alert"
> >
{onDismiss && ( {onDismiss && (
<button <button

View File

@ -9,7 +9,7 @@ import React, { useRef, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useClickOutside, useFocusTrap } from "../utils/hooks"; import { useClickOutside } from "../utils/hooks";
import Portal from "../utils/Portal"; import Portal from "../utils/Portal";
import "./Modal.css"; import "./Modal.css";
@ -29,11 +29,10 @@ Modal.propTypes = {
}; };
export function Modal({ shown, setShown, scrollable, size, children }) { export function Modal({ shown, setShown, scrollable, size, children }) {
const modalRef = useRef(); const dialogRef = useRef();
let modalSize = "modal-"; let modalSize = "modal-";
useClickOutside(modalRef, () => setShown(false)); useClickOutside(dialogRef, () => setShown(false));
useFocusTrap(modalRef, shown);
useEffect(() => { useEffect(() => {
const handleEsc = (event) => { const handleEsc = (event) => {
@ -66,13 +65,11 @@ export function Modal({ shown, setShown, scrollable, size, children }) {
return ( return (
<Portal containerId="modal-container"> <Portal containerId="modal-container">
<div <div
ref={modalRef}
className={`modal fade ${shown ? "show" : ""}`.trim()} className={`modal fade ${shown ? "show" : ""}`.trim()}
role="dialog" role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
> >
<div <div
ref={dialogRef}
className={`${modalSize.trim()} modal-dialog modal-dialog-centered ${ className={`${modalSize.trim()} modal-dialog modal-dialog-centered ${
scrollable ? "modal-dialog-scrollable" : "" scrollable ? "modal-dialog-scrollable" : ""
}`.trim()} }`.trim()}
@ -93,7 +90,7 @@ ModalHeader.propTypes = {
export function ModalHeader({ setShown, title }) { export function ModalHeader({ setShown, title }) {
return ( return (
<div className="modal-header"> <div className="modal-header">
<h1 className="modal-title fs-5">{title}</h1> <h5 className="modal-title">{title}</h5>
<button <button
type="button" type="button"
className="btn-close" className="btn-close"

View File

@ -26,7 +26,7 @@ function ResetWiFiSettings({ ws, endpoint }) {
useEffect(() => { useEffect(() => {
const module = "wifi"; const module = "wifi";
ws.subscribe(module).bind(module, "reset", () => { ws.bind(module, "reset", () => {
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
setTimeout(() => location.reload(), 1000); setTimeout(() => location.reload(), 1000);
}); });

View File

@ -46,7 +46,7 @@ describe("<RebootButton/>", () => {
fireEvent.click(getByText(componentContainer, "Reboot")); fireEvent.click(getByText(componentContainer, "Reboot"));
fireEvent.click(getByText(componentContainer, "Confirm reboot")); fireEvent.click(getByText(componentContainer, "Confirm reboot"));
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockAxios.post).toHaveBeenCalledWith(
"/reforis/api/reboot", "/api/reboot",
undefined, undefined,
expect.anything() expect.anything()
); );

View File

@ -6,8 +6,6 @@ exports[`<RebootButton/> Render modal. 1`] = `
id="modal-container" id="modal-container"
> >
<div <div
aria-labelledby="modal-title"
aria-modal="true"
class="modal fade show" class="modal fade show"
role="dialog" role="dialog"
> >
@ -21,11 +19,11 @@ exports[`<RebootButton/> Render modal. 1`] = `
<div <div
class="modal-header" class="modal-header"
> >
<h1 <h5
class="modal-title fs-5" class="modal-title"
> >
Warning! Warning!
</h1> </h5>
<button <button
aria-label="Close" aria-label="Close"
class="btn-close" class="btn-close"

View File

@ -7,7 +7,6 @@ exports[`AlertContext should render alert 1`] = `
> >
<div <div
class="alert alert-danger alert-dismissible" class="alert alert-danger alert-dismissible"
role="alert"
> >
<button <button
aria-label="Close" aria-label="Close"

View File

@ -10,3 +10,4 @@ global._ = (str) => str;
global.ngettext = (str) => str; global.ngettext = (str) => str;
global.babel = { format: (str) => str }; global.babel = { format: (str) => str };
global.ForisTranslations = { locale: "en" }; global.ForisTranslations = { locale: "en" };
global.setImmediate = (fn) => setTimeout(fn, 0);

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
*/ */
export const REFORIS_URL_PREFIX = "/reforis"; export const REFORIS_URL_PREFIX = process.env.REFORIS_PREFIX || "";
export const REFORIS_API_URL_PREFIX = `${REFORIS_URL_PREFIX}/api`; export const REFORIS_API_URL_PREFIX = `${REFORIS_URL_PREFIX}/api`;
export const ForisURLs = { export const ForisURLs = {

View File

@ -40,40 +40,3 @@ export function useClickOutside(element, callback) {
}; };
}); });
} }
/* Trap focus inside element. */
export function useFocusTrap(elementRef, condition = true) {
useEffect(() => {
if (!condition) {
return;
}
const currentElement = elementRef.current;
const focusableElements = currentElement.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
const handleTab = (event) => {
if (event.key === "Tab") {
if (event.shiftKey) {
if (document.activeElement === firstElement) {
lastElement.focus();
event.preventDefault();
}
} else if (document.activeElement === lastElement) {
firstElement.focus();
event.preventDefault();
}
}
};
currentElement.addEventListener("keydown", handleTab);
firstElement.focus();
return () => {
currentElement.removeEventListener("keydown", handleTab);
};
}, [elementRef, condition]);
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020-2022 CZ.NIC z.s.p.o. (http://www.nic.cz/) * Copyright (C) 2020-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
* *
* This is free software, licensed under the GNU General Public License v3. * This is free software, licensed under the GNU General Public License v3.
* See /LICENSE for more information. * See /LICENSE for more information.
@ -7,47 +7,33 @@
/* eslint no-console: "off" */ /* eslint no-console: "off" */
const PROTOCOL = window.location.protocol === "http:" ? "ws" : "wss"; import { io } from "socket.io-client";
const URL = process.env.LIGHTTPD import { REFORIS_URL_PREFIX } from "../utils/forisUrls";
? `${PROTOCOL}://${window.location.host}/${process.env.WSPATH || "foris-ws"}`
: `${PROTOCOL}://${window.location.hostname}:9081`;
const WAITING_FOR_CONNECTION_TIMEOUT = 500;
class WebSockets { class WebSockets {
constructor() { constructor() {
this.ws = new WebSocket(URL); this.socket = io("/notifications", {
this.ws.onerror = (e) => { path: `${REFORIS_URL_PREFIX}/reforis-ws`,
console.error("WS: Error:", e); });
}; this.connection = null;
this.ws.onmessage = (e) => { this.socket.on("disconnect", (reason) => {
console.debug(`WS: Received Message: ${e.data}`); this.connection = null;
const data = JSON.parse(e.data); console.debug(`SocketIO disconnected (${reason})`);
this.dispatch(data); });
}; this.socket.on("notification", (message) => {
this.ws.onopen = () => { console.debug("WS: Received Message:", message);
console.debug("WS: Connection open."); this.dispatch(message);
}; });
this.ws.onclose = () => { this.socket.on("connect", (connection) => {
console.debug("WS: Connection closed."); this.connection = connection;
}; console.debug(`SocketIO connected.`);
});
// callbacks[module][action] // callbacks[module][action]
this.callbacks = {}; this.callbacks = {};
} }
waitForConnection(callback) {
if (this.ws.readyState === 1) {
callback();
} else {
const that = this;
setTimeout(() => {
that.waitForConnection(callback);
}, WAITING_FOR_CONNECTION_TIMEOUT);
}
}
bind(module, action, callback) { bind(module, action, callback) {
this.callbacks[module] = this.callbacks[module] || {}; this.callbacks[module] = this.callbacks[module] || {};
this.callbacks[module][action] = this.callbacks[module][action] || []; this.callbacks[module][action] = this.callbacks[module][action] || [];
@ -55,13 +41,6 @@ class WebSockets {
return this; return this;
} }
subscribe(module) {
this.waitForConnection(() => {
this.send("subscribe", module);
});
return this;
}
unbind(module, action, callback) { unbind(module, action, callback) {
const callbacks = this.callbacks[module][action]; const callbacks = this.callbacks[module][action];
@ -75,28 +54,12 @@ class WebSockets {
} }
if (Object.keys(this.callbacks[module]).length === 0) { if (Object.keys(this.callbacks[module]).length === 0) {
this.unsubscribe(module); delete this.callbacks[module];
} }
return this; return this;
} }
unsubscribe(module) {
this.waitForConnection(() => {
this.send("unsubscribe", module);
delete this.callbacks[module];
});
return this;
}
send(action, params) {
const payload = JSON.stringify({ action, params });
this.waitForConnection(() => {
this.ws.send(payload);
});
return this;
}
dispatch(json) { dispatch(json) {
if (!json.module) return; if (!json.module) return;
@ -105,20 +68,17 @@ class WebSockets {
chain = this.callbacks[json.module][json.action]; chain = this.callbacks[json.module][json.action];
} catch (error) { } catch (error) {
if (error instanceof TypeError) { if (error instanceof TypeError) {
console.warn( console.debug(
`Callback for this message wasn't found:${error.data}` `Callbacks for this module wasn't found: ${json.module}`
); );
} else throw error; } else throw error;
} }
if (typeof chain === "undefined") return; if (typeof chain === "undefined") return;
console.debug("Handling WS message", json);
chain.forEach((callback) => callback(json)); chain.forEach((callback) => callback(json));
} }
close() {
this.ws.close();
}
} }
export default WebSockets; export default WebSockets;

View File

@ -33,7 +33,7 @@ function useWSForisModule(
setData(message.data); setData(message.data);
} }
ws.subscribe(module).bind(module, action, callback); ws.bind(module, action, callback);
return () => { return () => {
ws.unbind(module, action, callback); ws.unbind(module, action, callback);